File indexing completed on 2024-06-23 05:07:02
0001 /*************************************************************************** 0002 * SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> * 0003 * * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later * 0005 ***************************************************************************/ 0006 0007 #include "itemmodifyhandler.h" 0008 0009 #include "connection.h" 0010 #include "handlerhelper.h" 0011 #include "private/externalpartstorage_p.h" 0012 #include "shared/akranges.h" 0013 #include "storage/datastore.h" 0014 #include "storage/dbconfig.h" 0015 #include "storage/itemqueryhelper.h" 0016 #include "storage/itemretriever.h" 0017 #include "storage/parthelper.h" 0018 #include "storage/partstreamer.h" 0019 #include "storage/parttypehelper.h" 0020 #include "storage/selectquerybuilder.h" 0021 #include "storage/transaction.h" 0022 0023 #include "akonadiserver_debug.h" 0024 0025 #include <algorithm> 0026 #include <functional> 0027 0028 using namespace Akonadi; 0029 using namespace Akonadi::Server; 0030 0031 static bool payloadChanged(const QSet<QByteArray> &changes) 0032 { 0033 return changes | AkRanges::Actions::any([](const auto &change) { 0034 return change.startsWith(AKONADI_PARAM_PLD); 0035 }); 0036 } 0037 0038 ItemModifyHandler::ItemModifyHandler(AkonadiServer &akonadi) 0039 : Handler(akonadi) 0040 { 0041 } 0042 0043 bool ItemModifyHandler::replaceFlags(const PimItem::List &items, const QSet<QByteArray> &flags, bool &flagsChanged) 0044 { 0045 Flag::List flagList = HandlerHelper::resolveFlags(flags); 0046 DataStore *store = connection()->storageBackend(); 0047 0048 // TODO: why doesn't this have the "Make sure we don't overwrite some local-only flags" code that itemcreatehandler has? 0049 if (!store->setItemsFlags(items, nullptr, flagList, &flagsChanged)) { 0050 qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::replaceFlags: Unable to replace flags"; 0051 return false; 0052 } 0053 0054 return true; 0055 } 0056 0057 bool ItemModifyHandler::addFlags(const PimItem::List &items, const QSet<QByteArray> &flags, bool &flagsChanged) 0058 { 0059 const Flag::List flagList = HandlerHelper::resolveFlags(flags); 0060 DataStore *store = connection()->storageBackend(); 0061 0062 if (!store->appendItemsFlags(items, flagList, &flagsChanged)) { 0063 qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::addFlags: Unable to add new item flags"; 0064 return false; 0065 } 0066 return true; 0067 } 0068 0069 bool ItemModifyHandler::deleteFlags(const PimItem::List &items, const QSet<QByteArray> &flags, bool &flagsChanged) 0070 { 0071 DataStore *store = connection()->storageBackend(); 0072 0073 QList<Flag> flagList; 0074 flagList.reserve(flags.size()); 0075 for (auto iter = flags.cbegin(), end = flags.cend(); iter != end; ++iter) { 0076 Flag flag = Flag::retrieveByName(QString::fromUtf8(*iter)); 0077 if (!flag.isValid()) { 0078 continue; 0079 } 0080 0081 flagList.append(flag); 0082 } 0083 0084 if (!store->removeItemsFlags(items, flagList, &flagsChanged)) { 0085 qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::deleteFlags: Unable to remove item flags"; 0086 return false; 0087 } 0088 return true; 0089 } 0090 0091 bool ItemModifyHandler::replaceTags(const PimItem::List &item, const Scope &tags, bool &tagsChanged) 0092 { 0093 const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()->context()); 0094 if (!connection()->storageBackend()->setItemsTags(item, tagList, &tagsChanged)) { 0095 qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::replaceTags: unable to replace tags"; 0096 return false; 0097 } 0098 return true; 0099 } 0100 0101 bool ItemModifyHandler::addTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) 0102 { 0103 const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()->context()); 0104 if (!connection()->storageBackend()->appendItemsTags(items, tagList, &tagsChanged)) { 0105 qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::addTags: Unable to add new item tags"; 0106 return false; 0107 } 0108 return true; 0109 } 0110 0111 bool ItemModifyHandler::deleteTags(const PimItem::List &items, const Scope &tags, bool &tagsChanged) 0112 { 0113 const Tag::List tagList = HandlerHelper::tagsFromScope(tags, connection()->context()); 0114 if (!connection()->storageBackend()->removeItemsTags(items, tagList, &tagsChanged)) { 0115 qCWarning(AKONADISERVER_LOG) << "ItemModifyHandler::deleteTags: Unable to remove item tags"; 0116 return false; 0117 } 0118 return true; 0119 } 0120 0121 bool ItemModifyHandler::parseStream() 0122 { 0123 const auto &cmd = Protocol::cmdCast<Protocol::ModifyItemsCommand>(m_command); 0124 0125 // parseCommand(); 0126 0127 DataStore *store = connection()->storageBackend(); 0128 Transaction transaction(store, QStringLiteral("STORE")); 0129 ExternalPartStorageTransaction storageTrx; 0130 // Set the same modification time for each item. 0131 QDateTime modificationtime = QDateTime::currentDateTimeUtc(); 0132 if (DbType::type(store->database()) != DbType::Sqlite) { 0133 // Remove milliseconds from the modificationtime. PSQL and MySQL don't 0134 // support milliseconds in DATETIME column, so FETCHed Items will report 0135 // time without milliseconds, while this command would return answer 0136 // with milliseconds 0137 modificationtime = modificationtime.addMSecs(-modificationtime.time().msec()); 0138 } 0139 0140 // retrieve selected items 0141 SelectQueryBuilder<PimItem> qb; 0142 qb.setForUpdate(); 0143 ItemQueryHelper::scopeToQuery(cmd.items(), connection()->context(), qb); 0144 if (!qb.exec()) { 0145 return failureResponse("Unable to retrieve items"); 0146 } 0147 PimItem::List pimItems = qb.result(); 0148 if (pimItems.isEmpty()) { 0149 return failureResponse("No items found"); 0150 } 0151 0152 for (int i = 0; i < pimItems.size(); ++i) { 0153 if (cmd.oldRevision() > -1) { 0154 // check for conflicts if a resources tries to overwrite an item with dirty payload 0155 const PimItem &pimItem = pimItems.at(i); 0156 if (connection()->isOwnerResource(pimItem)) { 0157 if (pimItem.dirty()) { 0158 const QString error = 0159 QStringLiteral("[LRCONFLICT] Resource %1 tries to modify item %2 (%3) (in collection %4) with dirty payload, aborting STORE."); 0160 return failureResponse( 0161 error.arg(pimItem.collection().resource().name()).arg(pimItem.id()).arg(pimItem.remoteId()).arg(pimItem.collectionId())); 0162 } 0163 } 0164 0165 // check and update revisions 0166 if (pimItem.rev() != cmd.oldRevision()) { 0167 const QString error = QStringLiteral( 0168 "[LLCONFLICT] Resource %1 tries to modify item %2 (%3) (in collection %4) with revision %5; the item was modified elsewhere and has " 0169 "revision %6, aborting STORE."); 0170 return failureResponse(error.arg(pimItem.collection().resource().name()) 0171 .arg(pimItem.id()) 0172 .arg(pimItem.remoteId()) 0173 .arg(pimItem.collectionId()) 0174 .arg(cmd.oldRevision()) 0175 .arg(pimItems.at(i).rev())); 0176 } 0177 } 0178 } 0179 0180 PimItem &item = pimItems.first(); 0181 0182 QSet<QByteArray> changes; 0183 qint64 partSizes = 0; 0184 qint64 size = 0; 0185 0186 bool flagsChanged = false; 0187 bool tagsChanged = false; 0188 0189 if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::AddedFlags) { 0190 if (!addFlags(pimItems, cmd.addedFlags(), flagsChanged)) { 0191 return failureResponse("Unable to add item flags"); 0192 } 0193 } 0194 0195 if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedFlags) { 0196 if (!deleteFlags(pimItems, cmd.removedFlags(), flagsChanged)) { 0197 return failureResponse("Unable to remove item flags"); 0198 } 0199 } 0200 0201 if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::Flags) { 0202 if (!replaceFlags(pimItems, cmd.flags(), flagsChanged)) { 0203 return failureResponse("Unable to reset flags"); 0204 } 0205 } 0206 0207 if (flagsChanged) { 0208 changes << AKONADI_PARAM_FLAGS; 0209 } 0210 0211 if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::AddedTags) { 0212 if (!addTags(pimItems, cmd.addedTags(), tagsChanged)) { 0213 return failureResponse("Unable to add item tags"); 0214 } 0215 } 0216 0217 if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedTags) { 0218 if (!deleteTags(pimItems, cmd.removedTags(), tagsChanged)) { 0219 return failureResponse("Unable to remove item tags"); 0220 } 0221 } 0222 0223 if (cmd.modifiedParts() & Protocol::ModifyItemsCommand::Tags) { 0224 if (!replaceTags(pimItems, cmd.tags(), tagsChanged)) { 0225 return failureResponse("Unable to reset item tags"); 0226 } 0227 } 0228 0229 if (tagsChanged) { 0230 changes << AKONADI_PARAM_TAGS; 0231 } 0232 0233 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemoteID) { 0234 if (item.remoteId() != cmd.remoteId() && !cmd.remoteId().isEmpty()) { 0235 if (!connection()->isOwnerResource(item)) { 0236 qCWarning(AKONADISERVER_LOG) << "Invalid attempt to modify the remoteID for item" << item.id() << "from" << item.remoteId() << "to" 0237 << cmd.remoteId(); 0238 return failureResponse("Only resources can modify remote identifiers"); 0239 } 0240 item.setRemoteId(cmd.remoteId()); 0241 changes << AKONADI_PARAM_REMOTEID; 0242 } 0243 } 0244 0245 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::GID) { 0246 if (item.gid() != cmd.gid()) { 0247 item.setGid(cmd.gid()); 0248 } 0249 changes << AKONADI_PARAM_GID; 0250 } 0251 0252 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemoteRevision) { 0253 if (item.remoteRevision() != cmd.remoteRevision()) { 0254 if (!connection()->isOwnerResource(item)) { 0255 return failureResponse("Only resources can modify remote revisions"); 0256 } 0257 item.setRemoteRevision(cmd.remoteRevision()); 0258 changes << AKONADI_PARAM_REMOTEREVISION; 0259 } 0260 } 0261 0262 if (item.isValid() && !cmd.dirty()) { 0263 item.setDirty(false); 0264 } 0265 0266 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Size) { 0267 size = cmd.itemSize(); 0268 changes << AKONADI_PARAM_SIZE; 0269 } 0270 0271 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::RemovedParts) { 0272 const auto removedParts = cmd.removedParts(); 0273 if (!removedParts.isEmpty()) { 0274 if (!store->removeItemParts(item, removedParts)) { 0275 return failureResponse("Unable to remove item parts"); 0276 } 0277 for (const QByteArray &part : removedParts) { 0278 changes.insert(part); 0279 } 0280 } 0281 } 0282 0283 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Parts) { 0284 PartStreamer streamer(connection(), item); 0285 const auto partNames = cmd.parts(); 0286 for (const QByteArray &partName : partNames) { 0287 qint64 partSize = 0; 0288 try { 0289 streamer.stream(true, partName, partSize); 0290 } catch (const PartStreamerException &e) { 0291 return failureResponse(e.what()); 0292 } 0293 0294 changes.insert(partName); 0295 partSizes += partSize; 0296 } 0297 } 0298 0299 if (item.isValid() && cmd.modifiedParts() & Protocol::ModifyItemsCommand::Attributes) { 0300 PartStreamer streamer(connection(), item); 0301 const Protocol::Attributes attrs = cmd.attributes(); 0302 for (auto iter = attrs.cbegin(), end = attrs.cend(); iter != end; ++iter) { 0303 bool changed = false; 0304 try { 0305 streamer.streamAttribute(true, iter.key(), iter.value(), &changed); 0306 } catch (const PartStreamerException &e) { 0307 return failureResponse(e.what()); 0308 } 0309 0310 if (changed) { 0311 changes.insert(iter.key()); 0312 } 0313 } 0314 } 0315 0316 QDateTime datetime; 0317 if (!changes.isEmpty() || cmd.invalidateCache() || !cmd.dirty()) { 0318 // update item size 0319 if (pimItems.size() == 1 && (size > 0 || partSizes > 0)) { 0320 pimItems.first().setSize(qMax(size, partSizes)); 0321 } 0322 0323 const bool onlyRemoteIdChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_REMOTEID)); 0324 const bool onlyRemoteRevisionChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_REMOTEREVISION)); 0325 const bool onlyRemoteIdAndRevisionChanged = 0326 (changes.size() == 2 && changes.contains(AKONADI_PARAM_REMOTEID) && changes.contains(AKONADI_PARAM_REMOTEREVISION)); 0327 const bool onlyFlagsChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_FLAGS)); 0328 const bool onlyGIDChanged = (changes.size() == 1 && changes.contains(AKONADI_PARAM_GID)); 0329 // If only the remote id and/or the remote revision changed, we don't have to increase the REV, 0330 // because these updates do not change the payload and can only be done by the owning resource -> no conflicts possible 0331 const bool revisionNeedsUpdate = 0332 (!changes.isEmpty() && !onlyRemoteIdChanged && !onlyRemoteRevisionChanged && !onlyRemoteIdAndRevisionChanged && !onlyGIDChanged); 0333 0334 // run update query and prepare change notifications 0335 for (int i = 0; i < pimItems.count(); ++i) { 0336 PimItem &item = pimItems[i]; 0337 if (revisionNeedsUpdate) { 0338 item.setRev(item.rev() + 1); 0339 } 0340 0341 item.setDatetime(modificationtime); 0342 item.setAtime(modificationtime); 0343 if (!connection()->isOwnerResource(item) && payloadChanged(changes)) { 0344 item.setDirty(true); 0345 } 0346 if (!item.update()) { 0347 return failureResponse("Unable to write item changes into the database"); 0348 } 0349 0350 if (cmd.invalidateCache()) { 0351 if (!store->invalidateItemCache(item)) { 0352 return failureResponse("Unable to invalidate item cache in the database"); 0353 } 0354 } 0355 0356 // flags change notification went separately during command parsing 0357 // GID-only changes are ignored to prevent resources from updating their storage when no actual change happened 0358 if (cmd.notify() && !changes.isEmpty() && !onlyFlagsChanged && !onlyGIDChanged) { 0359 // Don't send FLAGS notification in itemChanged 0360 changes.remove(AKONADI_PARAM_FLAGS); 0361 store->notificationCollector()->itemChanged(item, changes); 0362 } 0363 0364 if (!cmd.noResponse()) { 0365 Protocol::ModifyItemsResponse resp; 0366 resp.setId(item.id()); 0367 resp.setNewRevision(item.rev()); 0368 sendResponse(std::move(resp)); 0369 } 0370 } 0371 0372 if (!transaction.commit()) { 0373 return failureResponse("Cannot commit transaction."); 0374 } 0375 // Always commit storage changes (deletion) after DB transaction 0376 storageTrx.commit(); 0377 0378 datetime = modificationtime; 0379 } else { 0380 datetime = pimItems.first().datetime(); 0381 } 0382 0383 Protocol::ModifyItemsResponse resp; 0384 resp.setModificationDateTime(datetime); 0385 return successResponse(std::move(resp)); 0386 }