Akonadi

itemmodifyjob.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2007 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "itemmodifyjob.h"
8#include "akonadicore_debug.h"
9#include "itemmodifyjob_p.h"
10
11#include "changemediator_p.h"
12#include "collection.h"
13#include "conflicthandler_p.h"
14#include "item_p.h"
15#include "itemserializer_p.h"
16#include "job_p.h"
17
18#include "gidextractor_p.h"
19#include "protocolhelper_p.h"
20
21#include <functional>
22
23#include <QFile>
24
25#include <span>
26
27using namespace Akonadi;
28
29ItemModifyJobPrivate::ItemModifyJobPrivate(ItemModifyJob *parent)
30 : JobPrivate(parent)
31{
32}
33
34void ItemModifyJobPrivate::setClean()
35{
36 mOperations.insert(Dirty);
37}
38
39Protocol::PartMetaData ItemModifyJobPrivate::preparePart(const QByteArray &partName)
40{
41 ProtocolHelper::PartNamespace ns; // dummy
42 const QByteArray partLabel = ProtocolHelper::decodePartIdentifier(partName, ns);
43 if (!mParts.contains(partLabel)) {
44 // Error?
45 return Protocol::PartMetaData();
46 }
47
48 mPendingData.clear();
49 int version = 0;
50 const auto item = mItems.first();
51 if (mForeignParts.contains(partLabel)) {
52 mPendingData = item.d_ptr->mPayloadPath.toUtf8();
53 const auto size = QFile(item.d_ptr->mPayloadPath).size();
54 return Protocol::PartMetaData(partName, size, version, Protocol::PartMetaData::Foreign);
55 } else {
56 ItemSerializer::serialize(mItems.first(), partLabel, mPendingData, version);
57 return Protocol::PartMetaData(partName, mPendingData.size(), version);
58 }
59}
60
61void ItemModifyJobPrivate::conflictResolved()
62{
63 Q_Q(ItemModifyJob);
64
65 q->setError(KJob::NoError);
66 q->setErrorText(QString());
67 q->emitResult();
68}
69
70void ItemModifyJobPrivate::conflictResolveError(const QString &message)
71{
72 Q_Q(ItemModifyJob);
73
74 q->setErrorText(q->errorText() + message);
75 q->emitResult();
76}
77
78void ItemModifyJobPrivate::doUpdateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision)
79{
80 auto it = std::find_if(mItems.begin(), mItems.end(), [&itemId](const Item &item) -> bool {
81 return item.id() == itemId;
82 });
83 if (it != mItems.end() && (*it).revision() == oldRevision) {
84 (*it).setRevision(newRevision);
85 }
86}
87
88QString ItemModifyJobPrivate::jobDebuggingString() const
89{
90 try {
91 mRemainingItems = std::span(mItems);
92 return Protocol::debugString(fullCommand());
93 } catch (const Exception &e) {
94 return QString::fromUtf8(e.what());
95 }
96}
97
98void ItemModifyJobPrivate::setSilent(bool silent)
99{
100 mSilent = silent;
101}
102
103bool ItemModifyJobPrivate::nextBatch()
104{
105 Q_Q(ItemModifyJob);
106
107 if (mRemainingItems.empty()) {
108 return true;
109 }
110
111 Protocol::ModifyItemsCommandPtr command;
112 try {
113 command = fullCommand();
114 } catch (const Exception &e) {
115 q->setError(Job::Unknown);
116 q->setErrorText(QString::fromUtf8(e.what()));
117 q->emitResult();
118 return true;
119 }
120
121 if (command->modifiedParts() == Protocol::ModifyItemsCommand::None) {
122 q->emitResult();
123 return true;
124 }
125
126 sendCommand(command);
127 return false;
128}
129
131 : Job(new ItemModifyJobPrivate(this), parent)
132{
134
135 d->mItems.append(item);
136 d->mParts = item.loadedPayloadParts();
137
138 d->mOperations.insert(ItemModifyJobPrivate::RemoteId);
139 d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision);
140
141 if (!item.payloadPath().isEmpty()) {
142 d->mForeignParts = ItemSerializer::allowedForeignParts(item);
143 }
144}
145
147 : Job(new ItemModifyJobPrivate(this), parent)
148{
149 Q_ASSERT(!items.isEmpty());
151 d->mItems = items;
152
153 // same as single item ctor
154 if (d->mItems.size() == 1) {
155 d->mParts = items.first().loadedPayloadParts();
156 d->mOperations.insert(ItemModifyJobPrivate::RemoteId);
157 d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision);
158 } else {
159 d->mIgnorePayload = true;
160 d->mRevCheck = false;
161 }
162}
163
165
166Protocol::ModifyItemsCommandPtr ItemModifyJobPrivate::fullCommand() const
167{
168 auto cmd = Protocol::ModifyItemsCommandPtr::create();
169
170 const Akonadi::Item &item = mRemainingItems.front();
171 for (int op : std::as_const(mOperations)) {
172 switch (op) {
173 case ItemModifyJobPrivate::RemoteId:
174 if (!item.remoteId().isNull()) {
175 cmd->setRemoteId(item.remoteId());
176 }
177 break;
178 case ItemModifyJobPrivate::Gid: {
179 const QString gid = GidExtractor::getGid(item);
180 if (!gid.isNull()) {
181 cmd->setGid(gid);
182 }
183 break;
184 }
185 case ItemModifyJobPrivate::RemoteRevision:
186 if (!item.remoteRevision().isNull()) {
187 cmd->setRemoteRevision(item.remoteRevision());
188 }
189 break;
190 case ItemModifyJobPrivate::Dirty:
191 cmd->setDirty(false);
192 break;
193 }
194 }
195
196 if (item.d_ptr->mClearPayload) {
197 cmd->setInvalidateCache(true);
198 }
199 if (mSilent) {
200 cmd->setNotify(true);
201 }
202
203 if (item.d_ptr->mFlagsOverwritten) {
204 cmd->setFlags(item.flags());
205 } else {
206 const auto addedFlags = ItemChangeLog::instance()->addedFlags(item.d_ptr);
207 if (!addedFlags.isEmpty()) {
208 cmd->setAddedFlags(addedFlags);
209 }
210 const auto deletedFlags = ItemChangeLog::instance()->deletedFlags(item.d_ptr);
211 if (!deletedFlags.isEmpty()) {
212 cmd->setRemovedFlags(deletedFlags);
213 }
214 }
215
216 if (item.d_ptr->mTagsOverwritten) {
217 const auto tags = item.tags();
218 if (!tags.isEmpty()) {
219 cmd->setTags(ProtocolHelper::entitySetToScope(tags));
220 }
221 } else {
222 const auto addedTags = ItemChangeLog::instance()->addedTags(item.d_ptr);
223 if (!addedTags.isEmpty()) {
224 cmd->setAddedTags(ProtocolHelper::entitySetToScope(addedTags));
225 }
226 const auto deletedTags = ItemChangeLog::instance()->deletedTags(item.d_ptr);
227 if (!deletedTags.isEmpty()) {
228 cmd->setRemovedTags(ProtocolHelper::entitySetToScope(deletedTags));
229 }
230 }
231
232 if (!mParts.isEmpty()) {
233 QSet<QByteArray> parts;
234 parts.reserve(mParts.size());
235 for (const QByteArray &part : std::as_const(mParts)) {
236 parts.insert(ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, part));
237 }
238 cmd->setParts(parts);
239 }
240
241 const AttributeStorage &attributeStorage = ItemChangeLog::instance()->attributeStorage(item.d_ptr);
242 const QSet<QByteArray> deletedAttributes = attributeStorage.deletedAttributes();
243 if (!deletedAttributes.isEmpty()) {
244 QSet<QByteArray> removedParts;
245 removedParts.reserve(deletedAttributes.size());
246 for (const QByteArray &part : deletedAttributes) {
247 removedParts.insert("ATR:" + part);
248 }
249 cmd->setRemovedParts(removedParts);
250 }
251 if (attributeStorage.hasModifiedAttributes()) {
252 cmd->setAttributes(ProtocolHelper::attributesToProtocol(attributeStorage.modifiedAttributes()));
253 }
254
255 // nothing to do
256 if (cmd->modifiedParts() == Protocol::ModifyItemsCommand::None && mParts.isEmpty() && !cmd->invalidateCache()) {
257 return cmd;
258 }
259
260 const auto batchSize = qMin(MaxBatchSize, mRemainingItems.size());
261 const auto batch = mRemainingItems.subspan(0, batchSize);
262 mRemainingItems = mRemainingItems.subspan(batch.size());
263
264 cmd->setItems(ProtocolHelper::entitySetToScope(QList(batch.begin(), batch.end())));
265 if (mRevCheck && item.revision() >= 0) {
266 cmd->setOldRevision(item.revision());
267 }
268
269 if (item.d_ptr->mSizeChanged) {
270 cmd->setItemSize(item.size());
271 }
272
273 return cmd;
274}
275
277{
279
280 d->mRemainingItems = std::span(d->mItems);
281 d->nextBatch();
282}
283
284bool ItemModifyJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response)
285{
287
288 if (!response->isResponse() && response->type() == Protocol::Command::StreamPayload) {
289 const auto &streamCmd = Protocol::cmdCast<Protocol::StreamPayloadCommand>(response);
290 auto streamResp = Protocol::StreamPayloadResponsePtr::create();
291 if (streamCmd.request() == Protocol::StreamPayloadCommand::MetaData) {
292 streamResp->setMetaData(d->preparePart(streamCmd.payloadName()));
293 } else {
294 if (streamCmd.destination().isEmpty()) {
295 streamResp->setData(d->mPendingData);
296 } else {
298 if (!ProtocolHelper::streamPayloadToFile(streamCmd.destination(), d->mPendingData, error)) {
299 // TODO: Error?
300 }
301 }
302 }
303 d->sendCommand(tag, streamResp);
304 return false;
305 }
306
307 if (response->isResponse() && response->type() == Protocol::Command::ModifyItems) {
308 const auto &resp = Protocol::cmdCast<Protocol::ModifyItemsResponse>(response);
309 if (resp.errorCode()) {
311 setErrorText(resp.errorMessage());
312 return true;
313 }
314
315 if (resp.errorMessage().contains(QLatin1StringView("[LLCONFLICT]"))) {
316 if (d->mAutomaticConflictHandlingEnabled) {
317 auto handler = new ConflictHandler(ConflictHandler::LocalLocalConflict, this);
318 handler->setConflictingItems(d->mItems.first(), d->mItems.first());
319 connect(handler, &ConflictHandler::conflictResolved, this, [d]() {
320 d->conflictResolved();
321 });
322 connect(handler, &ConflictHandler::error, this, [d](const QString &str) {
323 d->conflictResolveError(str);
324 });
325 QMetaObject::invokeMethod(handler, &ConflictHandler::start, Qt::QueuedConnection);
326 return true;
327 }
328 }
329
330 if (resp.modificationDateTime().isValid()) {
331 Item &item = d->mItems.first();
332 item.setModificationTime(resp.modificationDateTime());
333 item.d_ptr->resetChangeLog();
334 } else if (resp.id() > -1) {
335 auto it = std::find_if(d->mItems.begin(), d->mItems.end(), [&resp](const Item &item) -> bool {
336 return item.id() == resp.id();
337 });
338 if (it == d->mItems.end()) {
339 qCDebug(AKONADICORE_LOG) << "Received STORE response for an item we did not modify: " << tag << Protocol::debugString(response);
340 return true;
341 }
342
343 const int newRev = resp.newRevision();
344 const int oldRev = (*it).revision();
345 if (newRev >= oldRev && newRev >= 0) {
346 d->itemRevisionChanged((*it).id(), oldRev, newRev);
347 (*it).setRevision(newRev);
348 }
349 // There will be more responses, either for other modified items,
350 // or the final response with invalid ID, but with modification datetime
351 return false;
352 }
353
354 if (!d->mRemainingItems.empty()) {
355 if (!d->nextBatch()) {
356 return false;
357 }
358 }
359
360 for (const Item &item : std::as_const(d->mItems)) {
361 ChangeMediator::invalidateItem(item);
362 }
363
364 return true;
365 }
366
367 return Job::doHandleResponse(tag, response);
368}
369
371{
373
374 if (d->mIgnorePayload == ignore) {
375 return;
376 }
377
378 d->mIgnorePayload = ignore;
379 if (d->mIgnorePayload) {
380 d->mParts = QSet<QByteArray>();
381 } else {
382 Q_ASSERT(!d->mItems.first().mimeType().isEmpty());
383 d->mParts = d->mItems.first().loadedPayloadParts();
384 }
385}
386
388{
389 Q_D(const ItemModifyJob);
390
391 return d->mIgnorePayload;
392}
393
395{
397 if (update && !updateGid()) {
398 d->mOperations.insert(ItemModifyJobPrivate::Gid);
399 } else {
400 d->mOperations.remove(ItemModifyJobPrivate::Gid);
401 }
402}
403
405{
406 Q_D(const ItemModifyJob);
407 return d->mOperations.contains(ItemModifyJobPrivate::Gid);
408}
409
411{
413
414 d->mRevCheck = false;
415}
416
418{
420
421 d->mAutomaticConflictHandlingEnabled = false;
422}
423
425{
426 Q_D(const ItemModifyJob);
427 Q_ASSERT(d->mItems.size() == 1);
428
429 return d->mItems.first();
430}
431
433{
434 Q_D(const ItemModifyJob);
435 return d->mItems;
436}
437
438#include "moc_itemmodifyjob.cpp"
Base class for exceptions used by the Akonadi library.
const char * what() const noexcept override
Returns the error message associated with this exception.
Definition exception.cpp:65
Job that modifies an existing item in the Akonadi storage.
void setIgnorePayload(bool ignore)
Sets whether the payload of the modified item shall be omitted from transmission to the Akonadi stora...
void disableRevisionCheck()
Disables the check of the revision number.
bool updateGid() const
Returns whether the GID should be updated.
~ItemModifyJob() override
Destroys the item modify job.
ItemModifyJob(const Item &item, QObject *parent=nullptr)
Creates a new item modify job.
bool ignorePayload() const
Returns whether the payload of the modified item shall be omitted from transmission to the Akonadi st...
void disableAutomaticConflictHandling()
Disables the automatic handling of conflicts.
Item item() const
Returns the modified and stored item including the changed revision number.
void setUpdateGid(bool update)
Sets whether the GID shall be updated either from the gid parameter or by extracting it from the payl...
void doStart() override
This method must be reimplemented in the concrete jobs.
Item::List items() const
Returns the modified and stored items including the changed revision number.
bool doHandleResponse(qint64 tag, const Protocol::CommandPtr &response) override
This method should be reimplemented in the concrete jobs in case you want to handle incoming data.
Represents a PIM item stored in Akonadi storage.
Definition item.h:100
QString remoteRevision() const
Returns the remote revision of the item.
Definition item.cpp:83
qint64 Id
Describes the unique id type.
Definition item.h:105
qint64 size() const
Returns the size of the items in bytes.
Definition item.cpp:337
Flags flags() const
Returns all flags of this item.
Definition item.cpp:175
int revision() const
Returns the revision number of the item.
Definition item.cpp:306
QString remoteId() const
Returns the remote id of the item.
Definition item.cpp:73
QList< Item > List
Describes a list of items.
Definition item.h:110
virtual bool doHandleResponse(qint64 tag, const Protocol::CommandPtr &response)
This method should be reimplemented in the concrete jobs in case you want to handle incoming data.
Definition job.cpp:381
Job(QObject *parent=nullptr)
Creates a new job.
Definition job.cpp:290
@ Unknown
Unknown error.
Definition job.h:102
void setErrorText(const QString &errorText)
int error() const
void setError(int errorCode)
Helper integration between Akonadi and Qt.
KLEO_EXPORT std::unique_ptr< GpgME::DefaultAssuanTransaction > sendCommand(std::shared_ptr< GpgME::Context > &assuanContext, const std::string &command, GpgME::Error &err)
NETWORKMANAGERQT_EXPORT QString version()
virtual qint64 size() const const override
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
iterator insert(const T &value)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
QString first(qsizetype n) const const
QString fromUtf8(QByteArrayView str)
bool isNull() const const
QueuedConnection
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:52:13 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.