Akonadi

itemmodifyjob.cpp
1 /*
2  SPDX-FileCopyrightText: 2006-2007 Volker Krause <[email protected]>
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 using namespace Akonadi;
26 
27 ItemModifyJobPrivate::ItemModifyJobPrivate(ItemModifyJob *parent)
28  : JobPrivate(parent)
29 {
30 }
31 
32 void ItemModifyJobPrivate::setClean()
33 {
34  mOperations.insert(Dirty);
35 }
36 
37 Protocol::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 
59 void ItemModifyJobPrivate::conflictResolved()
60 {
61  Q_Q(ItemModifyJob);
62 
63  q->setError(KJob::NoError);
64  q->setErrorText(QString());
65  q->emitResult();
66 }
67 
68 void ItemModifyJobPrivate::conflictResolveError(const QString &message)
69 {
70  Q_Q(ItemModifyJob);
71 
72  q->setErrorText(q->errorText() + message);
73  q->emitResult();
74 }
75 
76 void 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 
86 QString 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 
95 void 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 
135 {
136 }
137 
138 Protocol::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 
266 bool ItemModifyJob::doHandleResponse(qint64 tag, const Protocol::CommandPtr &response)
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()) {
292  setError(Unknown);
293  setErrorText(resp.errorMessage());
294  return true;
295  }
296 
297  if (resp.errorMessage().contains(QLatin1String("[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  });
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"
bool isNull() const const
bool isEmpty() const const
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.
QString fromUtf8(const char *str, int size)
@ Unknown
Unknown error.
Definition: job.h:102
void setErrorText(const QString &errorText)
Flags flags() const
Returns all flags of this item.
Definition: item.cpp:175
Q_SCRIPTABLE Q_NOREPLY void start()
void reserve(int size)
void setUpdateGid(bool update)
Sets whether the GID shall be updated either from the gid parameter or by extracting it from the payl...
int revision() const
Returns the revision number of the item.
Definition: item.cpp:311
T & first()
int size() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
qint64 size() const
Returns the size of the items in bytes.
Definition: item.cpp:342
Item item() const
Returns the modified and stored item including the changed revision number.
qint64 Id
Describes the unique id type.
Definition: item.h:110
void setModificationTime(const QDateTime &datetime)
Sets the timestamp of the last modification of this item.
Definition: item.cpp:225
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.
Definition: itemmodifyjob.h:81
QString remoteRevision() const
Returns the remote revision of the item.
Definition: item.cpp:83
bool isEmpty() const const
Base class for exceptions used by the Akonadi library.
Definition: exceptionbase.h:29
void setRemoteId(const QString &id)
Sets the remote id of the item.
Definition: item.cpp:68
QueuedConnection
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 ignorePayload() const
Returns whether the payload of the modified item shall be omitted from transmission to the Akonadi st...
Base class for all actions in the Akonadi storage.
Definition: job.h:80
QString payloadPath() const
Returns path to the payload file set by setPayloadPath()
Definition: item.cpp:516
virtual qint64 size() const const override
unsigned int version()
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
void disableAutomaticConflictHandling()
Disables the automatic handling of conflicts.
~ItemModifyJob() override
Destroys the item modify job.
void doStart() override
This method must be reimplemented in the concrete jobs.
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QSet::iterator insert(const T &value)
bool updateGid() const
Returns whether the GID should be updated.
void emitResult()
int error() const
QSet< QByteArray > loadedPayloadParts() const
Returns the list of loaded payload parts.
Definition: item.cpp:288
ItemModifyJob(const Item &item, QObject *parent=nullptr)
Creates a new item modify job.
Item::List items() const
Returns the modified and stored items including the changed revision number.
void setError(int errorCode)
QString message
bool isEmpty() const const
QString remoteId() const
Returns the remote id of the item.
Definition: item.cpp:73
Represents a PIM item stored in Akonadi storage.
Definition: item.h:104
Q_D(Todo)
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jun 30 2022 03:51:46 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.