Akonadi

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

KDE's Doxygen guidelines are available online.