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
25using namespace Akonadi;
26
27ItemModifyJobPrivate::ItemModifyJobPrivate(ItemModifyJob *parent)
28 : JobPrivate(parent)
29{
30}
31
32void ItemModifyJobPrivate::setClean()
33{
34 mOperations.insert(Dirty);
35}
36
37Protocol::PartMetaData ItemModifyJobPrivate::preparePart(const QByteArray &partName)
38{
39 ProtocolHelper::PartNamespace ns; // dummy
40 const QByteArray partLabel = ProtocolHelper::decodePartIdentifier(partName, ns);
41 if (!mParts.contains(partLabel)) {
42 // Error?
43 return Protocol::PartMetaData();
44 }
45
46 mPendingData.clear();
47 int version = 0;
48 const auto item = mItems.first();
49 if (mForeignParts.contains(partLabel)) {
50 mPendingData = item.d_ptr->mPayloadPath.toUtf8();
51 const auto size = QFile(item.d_ptr->mPayloadPath).size();
52 return Protocol::PartMetaData(partName, size, version, Protocol::PartMetaData::Foreign);
53 } else {
54 ItemSerializer::serialize(mItems.first(), partLabel, mPendingData, version);
55 return Protocol::PartMetaData(partName, mPendingData.size(), version);
56 }
57}
58
59void ItemModifyJobPrivate::conflictResolved()
60{
61 Q_Q(ItemModifyJob);
62
63 q->setError(KJob::NoError);
64 q->setErrorText(QString());
65 q->emitResult();
66}
67
68void ItemModifyJobPrivate::conflictResolveError(const QString &message)
69{
70 Q_Q(ItemModifyJob);
71
72 q->setErrorText(q->errorText() + message);
73 q->emitResult();
74}
75
76void ItemModifyJobPrivate::doUpdateItemRevision(Akonadi::Item::Id itemId, int oldRevision, int newRevision)
77{
78 auto it = std::find_if(mItems.begin(), mItems.end(), [&itemId](const Item &item) -> bool {
79 return item.id() == itemId;
80 });
81 if (it != mItems.end() && (*it).revision() == oldRevision) {
82 (*it).setRevision(newRevision);
83 }
84}
85
86QString ItemModifyJobPrivate::jobDebuggingString() const
87{
88 try {
89 return Protocol::debugString(fullCommand());
90 } catch (const Exception &e) {
91 return QString::fromUtf8(e.what());
92 }
93}
94
95void ItemModifyJobPrivate::setSilent(bool silent)
96{
97 mSilent = silent;
98}
99
101 : Job(new ItemModifyJobPrivate(this), parent)
102{
104
105 d->mItems.append(item);
106 d->mParts = item.loadedPayloadParts();
107
108 d->mOperations.insert(ItemModifyJobPrivate::RemoteId);
109 d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision);
110
111 if (!item.payloadPath().isEmpty()) {
112 d->mForeignParts = ItemSerializer::allowedForeignParts(item);
113 }
114}
115
117 : Job(new ItemModifyJobPrivate(this), parent)
118{
119 Q_ASSERT(!items.isEmpty());
121 d->mItems = items;
122
123 // same as single item ctor
124 if (d->mItems.size() == 1) {
125 d->mParts = items.first().loadedPayloadParts();
126 d->mOperations.insert(ItemModifyJobPrivate::RemoteId);
127 d->mOperations.insert(ItemModifyJobPrivate::RemoteRevision);
128 } else {
129 d->mIgnorePayload = true;
130 d->mRevCheck = false;
131 }
132}
133
137
138Protocol::ModifyItemsCommandPtr ItemModifyJobPrivate::fullCommand() const
139{
140 auto cmd = Protocol::ModifyItemsCommandPtr::create();
141
142 const Akonadi::Item item = mItems.first();
143 for (int op : std::as_const(mOperations)) {
144 switch (op) {
145 case ItemModifyJobPrivate::RemoteId:
146 if (!item.remoteId().isNull()) {
147 cmd->setRemoteId(item.remoteId());
148 }
149 break;
150 case ItemModifyJobPrivate::Gid: {
151 const QString gid = GidExtractor::getGid(item);
152 if (!gid.isNull()) {
153 cmd->setGid(gid);
154 }
155 break;
156 }
157 case ItemModifyJobPrivate::RemoteRevision:
158 if (!item.remoteRevision().isNull()) {
159 cmd->setRemoteRevision(item.remoteRevision());
160 }
161 break;
162 case ItemModifyJobPrivate::Dirty:
163 cmd->setDirty(false);
164 break;
165 }
166 }
167
168 if (item.d_ptr->mClearPayload) {
169 cmd->setInvalidateCache(true);
170 }
171 if (mSilent) {
172 cmd->setNotify(true);
173 }
174
175 if (item.d_ptr->mFlagsOverwritten) {
176 cmd->setFlags(item.flags());
177 } else {
178 const auto addedFlags = ItemChangeLog::instance()->addedFlags(item.d_ptr);
179 if (!addedFlags.isEmpty()) {
180 cmd->setAddedFlags(addedFlags);
181 }
182 const auto deletedFlags = ItemChangeLog::instance()->deletedFlags(item.d_ptr);
183 if (!deletedFlags.isEmpty()) {
184 cmd->setRemovedFlags(deletedFlags);
185 }
186 }
187
188 if (item.d_ptr->mTagsOverwritten) {
189 const auto tags = item.tags();
190 if (!tags.isEmpty()) {
191 cmd->setTags(ProtocolHelper::entitySetToScope(tags));
192 }
193 } else {
194 const auto addedTags = ItemChangeLog::instance()->addedTags(item.d_ptr);
195 if (!addedTags.isEmpty()) {
196 cmd->setAddedTags(ProtocolHelper::entitySetToScope(addedTags));
197 }
198 const auto deletedTags = ItemChangeLog::instance()->deletedTags(item.d_ptr);
199 if (!deletedTags.isEmpty()) {
200 cmd->setRemovedTags(ProtocolHelper::entitySetToScope(deletedTags));
201 }
202 }
203
204 if (!mParts.isEmpty()) {
205 QSet<QByteArray> parts;
206 parts.reserve(mParts.size());
207 for (const QByteArray &part : std::as_const(mParts)) {
208 parts.insert(ProtocolHelper::encodePartIdentifier(ProtocolHelper::PartPayload, part));
209 }
210 cmd->setParts(parts);
211 }
212
213 const AttributeStorage &attributeStorage = ItemChangeLog::instance()->attributeStorage(item.d_ptr);
214 const QSet<QByteArray> deletedAttributes = attributeStorage.deletedAttributes();
215 if (!deletedAttributes.isEmpty()) {
216 QSet<QByteArray> removedParts;
217 removedParts.reserve(deletedAttributes.size());
218 for (const QByteArray &part : deletedAttributes) {
219 removedParts.insert("ATR:" + part);
220 }
221 cmd->setRemovedParts(removedParts);
222 }
223 if (attributeStorage.hasModifiedAttributes()) {
224 cmd->setAttributes(ProtocolHelper::attributesToProtocol(attributeStorage.modifiedAttributes()));
225 }
226
227 // nothing to do
228 if (cmd->modifiedParts() == Protocol::ModifyItemsCommand::None && mParts.isEmpty() && !cmd->invalidateCache()) {
229 return cmd;
230 }
231
232 cmd->setItems(ProtocolHelper::entitySetToScope(mItems));
233 if (mRevCheck && item.revision() >= 0) {
234 cmd->setOldRevision(item.revision());
235 }
236
237 if (item.d_ptr->mSizeChanged) {
238 cmd->setItemSize(item.size());
239 }
240
241 return cmd;
242}
243
245{
247
248 Protocol::ModifyItemsCommandPtr command;
249 try {
250 command = d->fullCommand();
251 } catch (const Exception &e) {
254 emitResult();
255 return;
256 }
257
258 if (command->modifiedParts() == Protocol::ModifyItemsCommand::None) {
259 emitResult();
260 return;
261 }
262
263 d->sendCommand(command);
264}
265
267{
269
270 if (!response->isResponse() && response->type() == Protocol::Command::StreamPayload) {
271 const auto &streamCmd = Protocol::cmdCast<Protocol::StreamPayloadCommand>(response);
272 auto streamResp = Protocol::StreamPayloadResponsePtr::create();
273 if (streamCmd.request() == Protocol::StreamPayloadCommand::MetaData) {
274 streamResp->setMetaData(d->preparePart(streamCmd.payloadName()));
275 } else {
276 if (streamCmd.destination().isEmpty()) {
277 streamResp->setData(d->mPendingData);
278 } else {
280 if (!ProtocolHelper::streamPayloadToFile(streamCmd.destination(), d->mPendingData, error)) {
281 // TODO: Error?
282 }
283 }
284 }
285 d->sendCommand(tag, streamResp);
286 return false;
287 }
288
289 if (response->isResponse() && response->type() == Protocol::Command::ModifyItems) {
290 const auto &resp = Protocol::cmdCast<Protocol::ModifyItemsResponse>(response);
291 if (resp.errorCode()) {
293 setErrorText(resp.errorMessage());
294 return true;
295 }
296
297 if (resp.errorMessage().contains(QLatin1StringView("[LLCONFLICT]"))) {
298 if (d->mAutomaticConflictHandlingEnabled) {
299 auto handler = new ConflictHandler(ConflictHandler::LocalLocalConflict, this);
300 handler->setConflictingItems(d->mItems.first(), d->mItems.first());
301 connect(handler, &ConflictHandler::conflictResolved, this, [d]() {
302 d->conflictResolved();
303 });
304 connect(handler, &ConflictHandler::error, this, [d](const QString &str) {
305 d->conflictResolveError(str);
306 });
307 QMetaObject::invokeMethod(handler, &ConflictHandler::start, Qt::QueuedConnection);
308 return true;
309 }
310 }
311
312 if (resp.modificationDateTime().isValid()) {
313 Item &item = d->mItems.first();
314 item.setModificationTime(resp.modificationDateTime());
315 item.d_ptr->resetChangeLog();
316 } else if (resp.id() > -1) {
317 auto it = std::find_if(d->mItems.begin(), d->mItems.end(), [&resp](const Item &item) -> bool {
318 return item.id() == resp.id();
319 });
320 if (it == d->mItems.end()) {
321 qCDebug(AKONADICORE_LOG) << "Received STORE response for an item we did not modify: " << tag << Protocol::debugString(response);
322 return true;
323 }
324
325 const int newRev = resp.newRevision();
326 const int oldRev = (*it).revision();
327 if (newRev >= oldRev && newRev >= 0) {
328 d->itemRevisionChanged((*it).id(), oldRev, newRev);
329 (*it).setRevision(newRev);
330 }
331 // There will be more responses, either for other modified items,
332 // or the final response with invalid ID, but with modification datetime
333 return false;
334 }
335
336 for (const Item &item : std::as_const(d->mItems)) {
337 ChangeMediator::invalidateItem(item);
338 }
339
340 return true;
341 }
342
343 return Job::doHandleResponse(tag, response);
344}
345
347{
349
350 if (d->mIgnorePayload == ignore) {
351 return;
352 }
353
354 d->mIgnorePayload = ignore;
355 if (d->mIgnorePayload) {
356 d->mParts = QSet<QByteArray>();
357 } else {
358 Q_ASSERT(!d->mItems.first().mimeType().isEmpty());
359 d->mParts = d->mItems.first().loadedPayloadParts();
360 }
361}
362
364{
365 Q_D(const ItemModifyJob);
366
367 return d->mIgnorePayload;
368}
369
371{
373 if (update && !updateGid()) {
374 d->mOperations.insert(ItemModifyJobPrivate::Gid);
375 } else {
376 d->mOperations.remove(ItemModifyJobPrivate::Gid);
377 }
378}
379
381{
382 Q_D(const ItemModifyJob);
383 return d->mOperations.contains(ItemModifyJobPrivate::Gid);
384}
385
387{
389
390 d->mRevCheck = false;
391}
392
394{
396
397 d->mAutomaticConflictHandlingEnabled = false;
398}
399
401{
402 Q_D(const ItemModifyJob);
403 Q_ASSERT(d->mItems.size() == 1);
404
405 return d->mItems.first();
406}
407
409{
410 Q_D(const ItemModifyJob);
411 return d->mItems;
412}
413
414#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 payloadPath() const
Returns path to the payload file set by setPayloadPath()
Definition item.cpp:524
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
void setModificationTime(const QDateTime &datetime)
Sets the timestamp of the last modification of this item.
Definition item.cpp:225
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
QSet< QByteArray > loadedPayloadParts() const
Returns the list of loaded payload parts.
Definition item.cpp:283
Base class for all actions in the Akonadi storage.
Definition job.h:81
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
@ Unknown
Unknown error.
Definition job.h:102
void setErrorText(const QString &errorText)
void emitResult()
int error() const
void setError(int errorCode)
Helper integration between Akonadi and Qt.
KDB_EXPORT KDbVersionInfo version()
virtual qint64 size() const const override
T & first()
bool isEmpty() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
iterator insert(const T &value)
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
bool isNull() const const
QueuedConnection
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:31:58 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.