File indexing completed on 2024-11-10 04:40:29

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
0003     SPDX-FileCopyrightText: 2007 Robert Zwerus <arzie@dds.nl>
0004     SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "itemcreatejob.h"
0010 
0011 #include "collection.h"
0012 #include "gidextractor_p.h"
0013 #include "item.h"
0014 #include "item_p.h"
0015 #include "itemserializer_p.h"
0016 #include "job_p.h"
0017 #include "private/protocol_p.h"
0018 #include "protocolhelper_p.h"
0019 
0020 #include <QFile>
0021 
0022 #include <KLocalizedString>
0023 
0024 using namespace Akonadi;
0025 
0026 class Akonadi::ItemCreateJobPrivate : public JobPrivate
0027 {
0028 public:
0029     explicit ItemCreateJobPrivate(ItemCreateJob *parent)
0030         : JobPrivate(parent)
0031     {
0032     }
0033 
0034     Protocol::PartMetaData preparePart(const QByteArray &part);
0035 
0036     QString jobDebuggingString() const override;
0037     Collection mCollection;
0038     Item mItem;
0039     QSet<QByteArray> mParts;
0040     QSet<QByteArray> mForeignParts;
0041     struct PendingPart {
0042         void clear()
0043         {
0044             name.clear();
0045             data.clear();
0046         }
0047 
0048         QByteArray name;
0049         QByteArray data;
0050     } mPendingPart;
0051     ItemCreateJob::MergeOptions mMergeOptions = ItemCreateJob::NoMerge;
0052     bool mItemReceived = false;
0053 };
0054 
0055 QString Akonadi::ItemCreateJobPrivate::jobDebuggingString() const
0056 {
0057     const QString collectionName = mCollection.name();
0058     QString str = QStringLiteral("%1 Item %2 from col %3")
0059                       .arg(mMergeOptions == ItemCreateJob::NoMerge ? QStringLiteral("Create") : QStringLiteral("Merge"))
0060                       .arg(mItem.id())
0061                       .arg(mCollection.id());
0062     if (!collectionName.isEmpty()) {
0063         str += QStringLiteral(" (%1)").arg(collectionName);
0064     }
0065     return str;
0066 }
0067 
0068 Protocol::PartMetaData ItemCreateJobPrivate::preparePart(const QByteArray &partName)
0069 {
0070     ProtocolHelper::PartNamespace ns; // dummy
0071     const QByteArray partLabel = ProtocolHelper::decodePartIdentifier(partName, ns);
0072     if (!mParts.remove(partLabel)) {
0073         // ERROR?
0074         return Protocol::PartMetaData();
0075     }
0076 
0077     int version = 0;
0078     if (mForeignParts.contains(partLabel)) {
0079         mPendingPart = PendingPart{.name = partName, .data = mItem.d_ptr->mPayloadPath.toUtf8()};
0080         const auto size = QFile(mItem.d_ptr->mPayloadPath).size();
0081         return Protocol::PartMetaData(partName, size, version, Protocol::PartMetaData::Foreign);
0082     } else {
0083         mPendingPart.clear();
0084         mPendingPart.name = partName;
0085         ItemSerializer::serialize(mItem, partLabel, mPendingPart.data, version);
0086         return Protocol::PartMetaData(partName, mPendingPart.data.size(), version);
0087     }
0088 }
0089 
0090 ItemCreateJob::ItemCreateJob(const Item &item, const Collection &collection, QObject *parent)
0091     : Job(new ItemCreateJobPrivate(this), parent)
0092 {
0093     Q_D(ItemCreateJob);
0094 
0095     Q_ASSERT(!item.mimeType().isEmpty());
0096     d->mItem = item;
0097     d->mParts = d->mItem.loadedPayloadParts();
0098     d->mCollection = collection;
0099 
0100     if (!d->mItem.payloadPath().isEmpty()) {
0101         d->mForeignParts = ItemSerializer::allowedForeignParts(d->mItem);
0102     }
0103 }
0104 
0105 ItemCreateJob::~ItemCreateJob()
0106 {
0107 }
0108 
0109 void ItemCreateJob::doStart()
0110 {
0111     Q_D(ItemCreateJob);
0112 
0113     if (!d->mCollection.isValid()) {
0114         setError(Unknown);
0115         setErrorText(i18n("Invalid parent collection"));
0116         emitResult();
0117         return;
0118     }
0119 
0120     auto cmd = Protocol::CreateItemCommandPtr::create();
0121     cmd->setMimeType(d->mItem.mimeType());
0122     cmd->setGid(d->mItem.gid());
0123     cmd->setRemoteId(d->mItem.remoteId());
0124     cmd->setRemoteRevision(d->mItem.remoteRevision());
0125     cmd->setModificationTime(d->mItem.modificationTime());
0126 
0127     Protocol::CreateItemCommand::MergeModes mergeModes = Protocol::CreateItemCommand::None;
0128     if ((d->mMergeOptions & GID) && !d->mItem.gid().isEmpty()) {
0129         mergeModes |= Protocol::CreateItemCommand::GID;
0130     }
0131     if ((d->mMergeOptions & RID) && !d->mItem.remoteId().isEmpty()) {
0132         mergeModes |= Protocol::CreateItemCommand::RemoteID;
0133     }
0134     if ((d->mMergeOptions & Silent)) {
0135         mergeModes |= Protocol::CreateItemCommand::Silent;
0136     }
0137     const bool merge = (mergeModes & Protocol::CreateItemCommand::GID) || (mergeModes & Protocol::CreateItemCommand::RemoteID);
0138     cmd->setMergeModes(mergeModes);
0139 
0140     if (d->mItem.d_ptr->mFlagsOverwritten || !merge) {
0141         cmd->setFlags(d->mItem.flags());
0142         cmd->setFlagsOverwritten(d->mItem.d_ptr->mFlagsOverwritten);
0143     } else {
0144         const auto addedFlags = ItemChangeLog::instance()->addedFlags(d->mItem.d_ptr);
0145         const auto deletedFlags = ItemChangeLog::instance()->deletedFlags(d->mItem.d_ptr);
0146         cmd->setAddedFlags(addedFlags);
0147         cmd->setRemovedFlags(deletedFlags);
0148     }
0149 
0150     if (d->mItem.d_ptr->mTagsOverwritten || !merge) {
0151         const auto tags = d->mItem.tags();
0152         if (!tags.isEmpty()) {
0153             cmd->setTags(ProtocolHelper::entitySetToScope(tags));
0154         }
0155     } else {
0156         const auto addedTags = ItemChangeLog::instance()->addedTags(d->mItem.d_ptr);
0157         if (!addedTags.isEmpty()) {
0158             cmd->setAddedTags(ProtocolHelper::entitySetToScope(addedTags));
0159         }
0160         const auto deletedTags = ItemChangeLog::instance()->deletedTags(d->mItem.d_ptr);
0161         if (!deletedTags.isEmpty()) {
0162             cmd->setRemovedTags(ProtocolHelper::entitySetToScope(deletedTags));
0163         }
0164     }
0165 
0166     cmd->setCollection(ProtocolHelper::entityToScope(d->mCollection));
0167     cmd->setItemSize(d->mItem.size());
0168 
0169     cmd->setAttributes(ProtocolHelper::attributesToProtocol(d->mItem));
0170     QSet<QByteArray> parts;
0171     parts.reserve(d->mParts.size());
0172     for (const QByteArray &part : std::as_const(d->mParts)) {
0173         parts.insert(ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, part));
0174     }
0175     cmd->setParts(parts);
0176 
0177     d->sendCommand(cmd);
0178 }
0179 
0180 bool ItemCreateJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response)
0181 {
0182     Q_D(ItemCreateJob);
0183 
0184     if (!response->isResponse() && response->type() == Protocol::Command::StreamPayload) {
0185         const auto &streamCmd = Protocol::cmdCast<Protocol::StreamPayloadCommand>(response);
0186         auto streamResp = Protocol::StreamPayloadResponsePtr::create();
0187         streamResp->setPayloadName(streamCmd.payloadName());
0188         if (streamCmd.request() == Protocol::StreamPayloadCommand::MetaData) {
0189             streamResp->setMetaData(d->preparePart(streamCmd.payloadName()));
0190         } else if (streamCmd.request() == Protocol::StreamPayloadCommand::Data) {
0191             if (streamCmd.payloadName() != d->mPendingPart.name) {
0192                 streamResp->setError(1, QStringLiteral("Unexpected payload name"));
0193             } else if (streamCmd.destination().isEmpty()) {
0194                 streamResp->setData(d->mPendingPart.data);
0195             } else {
0196                 QByteArray error;
0197                 if (!ProtocolHelper::streamPayloadToFile(streamCmd.destination(), d->mPendingPart.data, error)) {
0198                     streamResp->setError(1, QStringLiteral("Failed to stream payload to file: %1").arg(QString::fromUtf8(error)));
0199                 }
0200             }
0201         } else {
0202             streamResp->setError(1, QStringLiteral("Unknown stream payload request"));
0203         }
0204         d->sendCommand(tag, streamResp);
0205         return false;
0206     }
0207 
0208     if (response->isResponse() && response->type() == Protocol::Command::FetchItems) {
0209         const auto &fetchResp = Protocol::cmdCast<Protocol::FetchItemsResponse>(response);
0210         Item item = ProtocolHelper::parseItemFetchResult(fetchResp);
0211         if (!item.isValid()) {
0212             // Error, maybe?
0213             return false;
0214         }
0215         d->mItem = item;
0216         return false;
0217     }
0218 
0219     if (response->isResponse() && response->type() == Protocol::Command::CreateItem) {
0220         return true;
0221     }
0222 
0223     return Job::doHandleResponse(tag, response);
0224 }
0225 
0226 void ItemCreateJob::setMerge(ItemCreateJob::MergeOptions options)
0227 {
0228     Q_D(ItemCreateJob);
0229 
0230     d->mMergeOptions = options;
0231 }
0232 
0233 Item ItemCreateJob::item() const
0234 {
0235     Q_D(const ItemCreateJob);
0236 
0237     // Parent collection is available only with non-silent merge/create
0238     if (d->mItem.parentCollection().isValid()) {
0239         return d->mItem;
0240     }
0241 
0242     Item item(d->mItem);
0243     item.setRevision(0);
0244     item.setParentCollection(d->mCollection);
0245     item.setStorageCollectionId(d->mCollection.id());
0246 
0247     return item;
0248 }
0249 
0250 #include "moc_itemcreatejob.cpp"