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"