File indexing completed on 2024-11-10 04:40:30
0001 /* 0002 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "itemmodifyjob.h" 0008 #include "akonadicore_debug.h" 0009 #include "itemmodifyjob_p.h" 0010 0011 #include "changemediator_p.h" 0012 #include "collection.h" 0013 #include "conflicthandler_p.h" 0014 #include "item_p.h" 0015 #include "itemserializer_p.h" 0016 #include "job_p.h" 0017 0018 #include "gidextractor_p.h" 0019 #include "protocolhelper_p.h" 0020 0021 #include <functional> 0022 0023 #include <QFile> 0024 0025 using namespace Akonadi; 0026 0027 ItemModifyJobPrivate::ItemModifyJobPrivate(ItemModifyJob *parent) 0028 : JobPrivate(parent) 0029 { 0030 } 0031 0032 void ItemModifyJobPrivate::setClean() 0033 { 0034 mOperations.insert(Dirty); 0035 } 0036 0037 Protocol::PartMetaData ItemModifyJobPrivate::preparePart(const QByteArray &partName) 0038 { 0039 ProtocolHelper::PartNamespace ns; // dummy 0040 const QByteArray partLabel = ProtocolHelper::decodePartIdentifier(partName, ns); 0041 if (!mParts.contains(partLabel)) { 0042 // Error? 0043 return Protocol::PartMetaData(); 0044 } 0045 0046 mPendingData.clear(); 0047 int version = 0; 0048 const auto item = mItems.first(); 0049 if (mForeignParts.contains(partLabel)) { 0050 mPendingData = item.d_ptr->mPayloadPath.toUtf8(); 0051 const auto size = QFile(item.d_ptr->mPayloadPath).size(); 0052 return Protocol::PartMetaData(partName, size, version, Protocol::PartMetaData::Foreign); 0053 } else { 0054 ItemSerializer::serialize(mItems.first(), partLabel, mPendingData, version); 0055 return Protocol::PartMetaData(partName, mPendingData.size(), version); 0056 } 0057 } 0058 0059 void ItemModifyJobPrivate::conflictResolved() 0060 { 0061 Q_Q(ItemModifyJob); 0062 0063 q->setError(KJob::NoError); 0064 q->setErrorText(QString()); 0065 q->emitResult(); 0066 } 0067 0068 void ItemModifyJobPrivate::conflictResolveError(const QString &message) 0069 { 0070 Q_Q(ItemModifyJob); 0071 0072 q->setErrorText(q->errorText() + message); 0073 q->emitResult(); 0074 } 0075 0076 void ItemModifyJobPrivate::doUpdateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision) 0077 { 0078 auto it = std::find_if(mItems.begin(), mItems.end(), [&itemId](const Item &item) -> bool { 0079 return item.id() == itemId; 0080 }); 0081 if (it != mItems.end() && (*it).revision() == oldRevision) { 0082 (*it).setRevision(newRevision); 0083 } 0084 } 0085 0086 QString ItemModifyJobPrivate::jobDebuggingString() const 0087 { 0088 try { 0089 return Protocol::debugString(fullCommand()); 0090 } catch (const Exception &e) { 0091 return QString::fromUtf8(e.what()); 0092 } 0093 } 0094 0095 void ItemModifyJobPrivate::setSilent(bool silent) 0096 { 0097 mSilent = silent; 0098 } 0099 0100 ItemModifyJob::ItemModifyJob(const Item &item, QObject *parent) 0101 : Job(new ItemModifyJobPrivate(this), parent) 0102 { 0103 Q_D(ItemModifyJob); 0104 0105 d->mItems.append(item); 0106 d->mParts = item.loadedPayloadParts(); 0107 0108 d->mOperations.insert(ItemModifyJobPrivate::RemoteId); 0109 d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision); 0110 0111 if (!item.payloadPath().isEmpty()) { 0112 d->mForeignParts = ItemSerializer::allowedForeignParts(item); 0113 } 0114 } 0115 0116 ItemModifyJob::ItemModifyJob(const Akonadi::Item::List &items, QObject *parent) 0117 : Job(new ItemModifyJobPrivate(this), parent) 0118 { 0119 Q_ASSERT(!items.isEmpty()); 0120 Q_D(ItemModifyJob); 0121 d->mItems = items; 0122 0123 // same as single item ctor 0124 if (d->mItems.size() == 1) { 0125 d->mParts = items.first().loadedPayloadParts(); 0126 d->mOperations.insert(ItemModifyJobPrivate::RemoteId); 0127 d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision); 0128 } else { 0129 d->mIgnorePayload = true; 0130 d->mRevCheck = false; 0131 } 0132 } 0133 0134 ItemModifyJob::~ItemModifyJob() 0135 { 0136 } 0137 0138 Protocol::ModifyItemsCommandPtr ItemModifyJobPrivate::fullCommand() const 0139 { 0140 auto cmd = Protocol::ModifyItemsCommandPtr::create(); 0141 0142 const Akonadi::Item item = mItems.first(); 0143 for (int op : std::as_const(mOperations)) { 0144 switch (op) { 0145 case ItemModifyJobPrivate::RemoteId: 0146 if (!item.remoteId().isNull()) { 0147 cmd->setRemoteId(item.remoteId()); 0148 } 0149 break; 0150 case ItemModifyJobPrivate::Gid: { 0151 const QString gid = GidExtractor::getGid(item); 0152 if (!gid.isNull()) { 0153 cmd->setGid(gid); 0154 } 0155 break; 0156 } 0157 case ItemModifyJobPrivate::RemoteRevision: 0158 if (!item.remoteRevision().isNull()) { 0159 cmd->setRemoteRevision(item.remoteRevision()); 0160 } 0161 break; 0162 case ItemModifyJobPrivate::Dirty: 0163 cmd->setDirty(false); 0164 break; 0165 } 0166 } 0167 0168 if (item.d_ptr->mClearPayload) { 0169 cmd->setInvalidateCache(true); 0170 } 0171 if (mSilent) { 0172 cmd->setNotify(true); 0173 } 0174 0175 if (item.d_ptr->mFlagsOverwritten) { 0176 cmd->setFlags(item.flags()); 0177 } else { 0178 const auto addedFlags = ItemChangeLog::instance()->addedFlags(item.d_ptr); 0179 if (!addedFlags.isEmpty()) { 0180 cmd->setAddedFlags(addedFlags); 0181 } 0182 const auto deletedFlags = ItemChangeLog::instance()->deletedFlags(item.d_ptr); 0183 if (!deletedFlags.isEmpty()) { 0184 cmd->setRemovedFlags(deletedFlags); 0185 } 0186 } 0187 0188 if (item.d_ptr->mTagsOverwritten) { 0189 const auto tags = item.tags(); 0190 if (!tags.isEmpty()) { 0191 cmd->setTags(ProtocolHelper::entitySetToScope(tags)); 0192 } 0193 } else { 0194 const auto addedTags = ItemChangeLog::instance()->addedTags(item.d_ptr); 0195 if (!addedTags.isEmpty()) { 0196 cmd->setAddedTags(ProtocolHelper::entitySetToScope(addedTags)); 0197 } 0198 const auto deletedTags = ItemChangeLog::instance()->deletedTags(item.d_ptr); 0199 if (!deletedTags.isEmpty()) { 0200 cmd->setRemovedTags(ProtocolHelper::entitySetToScope(deletedTags)); 0201 } 0202 } 0203 0204 if (!mParts.isEmpty()) { 0205 QSet<QByteArray> parts; 0206 parts.reserve(mParts.size()); 0207 for (const QByteArray &part : std::as_const(mParts)) { 0208 parts.insert(ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, part)); 0209 } 0210 cmd->setParts(parts); 0211 } 0212 0213 const AttributeStorage &attributeStorage = ItemChangeLog::instance()->attributeStorage(item.d_ptr); 0214 const QSet<QByteArray> deletedAttributes = attributeStorage.deletedAttributes(); 0215 if (!deletedAttributes.isEmpty()) { 0216 QSet<QByteArray> removedParts; 0217 removedParts.reserve(deletedAttributes.size()); 0218 for (const QByteArray &part : deletedAttributes) { 0219 removedParts.insert("ATR:" + part); 0220 } 0221 cmd->setRemovedParts(removedParts); 0222 } 0223 if (attributeStorage.hasModifiedAttributes()) { 0224 cmd->setAttributes(ProtocolHelper::attributesToProtocol(attributeStorage.modifiedAttributes())); 0225 } 0226 0227 // nothing to do 0228 if (cmd->modifiedParts() == Protocol::ModifyItemsCommand::None && mParts.isEmpty() && !cmd->invalidateCache()) { 0229 return cmd; 0230 } 0231 0232 cmd->setItems(ProtocolHelper::entitySetToScope(mItems)); 0233 if (mRevCheck && item.revision() >= 0) { 0234 cmd->setOldRevision(item.revision()); 0235 } 0236 0237 if (item.d_ptr->mSizeChanged) { 0238 cmd->setItemSize(item.size()); 0239 } 0240 0241 return cmd; 0242 } 0243 0244 void ItemModifyJob::doStart() 0245 { 0246 Q_D(ItemModifyJob); 0247 0248 Protocol::ModifyItemsCommandPtr command; 0249 try { 0250 command = d->fullCommand(); 0251 } catch (const Exception &e) { 0252 setError(Job::Unknown); 0253 setErrorText(QString::fromUtf8(e.what())); 0254 emitResult(); 0255 return; 0256 } 0257 0258 if (command->modifiedParts() == Protocol::ModifyItemsCommand::None) { 0259 emitResult(); 0260 return; 0261 } 0262 0263 d->sendCommand(command); 0264 } 0265 0266 bool ItemModifyJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) 0267 { 0268 Q_D(ItemModifyJob); 0269 0270 if (!response->isResponse() && response->type() == Protocol::Command::StreamPayload) { 0271 const auto &streamCmd = Protocol::cmdCast<Protocol::StreamPayloadCommand>(response); 0272 auto streamResp = Protocol::StreamPayloadResponsePtr::create(); 0273 if (streamCmd.request() == Protocol::StreamPayloadCommand::MetaData) { 0274 streamResp->setMetaData(d->preparePart(streamCmd.payloadName())); 0275 } else { 0276 if (streamCmd.destination().isEmpty()) { 0277 streamResp->setData(d->mPendingData); 0278 } else { 0279 QByteArray error; 0280 if (!ProtocolHelper::streamPayloadToFile(streamCmd.destination(), d->mPendingData, error)) { 0281 // TODO: Error? 0282 } 0283 } 0284 } 0285 d->sendCommand(tag, streamResp); 0286 return false; 0287 } 0288 0289 if (response->isResponse() && response->type() == Protocol::Command::ModifyItems) { 0290 const auto &resp = Protocol::cmdCast<Protocol::ModifyItemsResponse>(response); 0291 if (resp.errorCode()) { 0292 setError(Unknown); 0293 setErrorText(resp.errorMessage()); 0294 return true; 0295 } 0296 0297 if (resp.errorMessage().contains(QLatin1StringView("[LLCONFLICT]"))) { 0298 if (d->mAutomaticConflictHandlingEnabled) { 0299 auto handler = new ConflictHandler(ConflictHandler::LocalLocalConflict, this); 0300 handler->setConflictingItems(d->mItems.first(), d->mItems.first()); 0301 connect(handler, &ConflictHandler::conflictResolved, this, [d]() { 0302 d->conflictResolved(); 0303 }); 0304 connect(handler, &ConflictHandler::error, this, [d](const QString &str) { 0305 d->conflictResolveError(str); 0306 }); 0307 QMetaObject::invokeMethod(handler, &ConflictHandler::start, Qt::QueuedConnection); 0308 return true; 0309 } 0310 } 0311 0312 if (resp.modificationDateTime().isValid()) { 0313 Item &item = d->mItems.first(); 0314 item.setModificationTime(resp.modificationDateTime()); 0315 item.d_ptr->resetChangeLog(); 0316 } else if (resp.id() > -1) { 0317 auto it = std::find_if(d->mItems.begin(), d->mItems.end(), [&resp](const Item &item) -> bool { 0318 return item.id() == resp.id(); 0319 }); 0320 if (it == d->mItems.end()) { 0321 qCDebug(AKONADICORE_LOG) << "Received STORE response for an item we did not modify: " << tag << Protocol::debugString(response); 0322 return true; 0323 } 0324 0325 const int newRev = resp.newRevision(); 0326 const int oldRev = (*it).revision(); 0327 if (newRev >= oldRev && newRev >= 0) { 0328 d->itemRevisionChanged((*it).id(), oldRev, newRev); 0329 (*it).setRevision(newRev); 0330 } 0331 // There will be more responses, either for other modified items, 0332 // or the final response with invalid ID, but with modification datetime 0333 return false; 0334 } 0335 0336 for (const Item &item : std::as_const(d->mItems)) { 0337 ChangeMediator::invalidateItem(item); 0338 } 0339 0340 return true; 0341 } 0342 0343 return Job::doHandleResponse(tag, response); 0344 } 0345 0346 void ItemModifyJob::setIgnorePayload(bool ignore) 0347 { 0348 Q_D(ItemModifyJob); 0349 0350 if (d->mIgnorePayload == ignore) { 0351 return; 0352 } 0353 0354 d->mIgnorePayload = ignore; 0355 if (d->mIgnorePayload) { 0356 d->mParts = QSet<QByteArray>(); 0357 } else { 0358 Q_ASSERT(!d->mItems.first().mimeType().isEmpty()); 0359 d->mParts = d->mItems.first().loadedPayloadParts(); 0360 } 0361 } 0362 0363 bool ItemModifyJob::ignorePayload() const 0364 { 0365 Q_D(const ItemModifyJob); 0366 0367 return d->mIgnorePayload; 0368 } 0369 0370 void ItemModifyJob::setUpdateGid(bool update) 0371 { 0372 Q_D(ItemModifyJob); 0373 if (update && !updateGid()) { 0374 d->mOperations.insert(ItemModifyJobPrivate::Gid); 0375 } else { 0376 d->mOperations.remove(ItemModifyJobPrivate::Gid); 0377 } 0378 } 0379 0380 bool ItemModifyJob::updateGid() const 0381 { 0382 Q_D(const ItemModifyJob); 0383 return d->mOperations.contains(ItemModifyJobPrivate::Gid); 0384 } 0385 0386 void ItemModifyJob::disableRevisionCheck() 0387 { 0388 Q_D(ItemModifyJob); 0389 0390 d->mRevCheck = false; 0391 } 0392 0393 void ItemModifyJob::disableAutomaticConflictHandling() 0394 { 0395 Q_D(ItemModifyJob); 0396 0397 d->mAutomaticConflictHandlingEnabled = false; 0398 } 0399 0400 Item ItemModifyJob::item() const 0401 { 0402 Q_D(const ItemModifyJob); 0403 Q_ASSERT(d->mItems.size() == 1); 0404 0405 return d->mItems.first(); 0406 } 0407 0408 Item::List ItemModifyJob::items() const 0409 { 0410 Q_D(const ItemModifyJob); 0411 return d->mItems; 0412 } 0413 0414 #include "moc_itemmodifyjob.cpp"