Akonadi

pastehelper.cpp
1 /*
2  SPDX-FileCopyrightText: 2008 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "pastehelper_p.h"
8 
9 #include "collectioncopyjob.h"
10 #include "collectionfetchjob.h"
11 #include "collectionmovejob.h"
12 #include "item.h"
13 #include "itemcopyjob.h"
14 #include "itemcreatejob.h"
15 #include "itemmodifyjob.h"
16 #include "itemmovejob.h"
17 #include "linkjob.h"
18 #include "session.h"
19 #include "transactionsequence.h"
20 #include "unlinkjob.h"
21 
22 #include "akonadicore_debug.h"
23 
24 #include <QUrl>
25 #include <QUrlQuery>
26 
27 #include <QByteArray>
28 #include <QMimeData>
29 
30 #include <functional>
31 
32 using namespace Akonadi;
33 
34 class PasteHelperJob : public Akonadi::TransactionSequence
35 {
36  Q_OBJECT
37 
38 public:
39  explicit PasteHelperJob(Qt::DropAction action,
40  const Akonadi::Item::List &items,
41  const Akonadi::Collection::List &collections,
42  const Akonadi::Collection &destination,
43  QObject *parent = nullptr);
44  ~PasteHelperJob() override;
45 
46 private Q_SLOTS:
47  void onDragSourceCollectionFetched(KJob *job);
48 
49 private:
50  void runActions();
51  void runItemsActions();
52  void runCollectionsActions();
53 
54 private:
55  Akonadi::Item::List mItems;
56  Akonadi::Collection::List mCollections;
57  Akonadi::Collection mDestCollection;
58  Qt::DropAction mAction;
59 };
60 
61 PasteHelperJob::PasteHelperJob(Qt::DropAction action,
62  const Item::List &items,
63  const Collection::List &collections,
64  const Collection &destination,
65  QObject *parent)
66  : TransactionSequence(parent)
67  , mItems(items)
68  , mCollections(collections)
69  , mDestCollection(destination)
70  , mAction(action)
71 {
72  // FIXME: The below code disables transactions in order to avoid data loss due to nested
73  // transactions (copy and colcopy in the server doesn't see the items retrieved into the cache and copies empty payloads).
74  // Remove once this is fixed properly, see the other FIXME comments.
75  setProperty("transactionsDisabled", true);
76 
77  Collection dragSourceCollection;
78  if (!items.isEmpty() && items.first().parentCollection().isValid()) {
79  // Check if all items have the same parent collection ID
80  const Collection parent = items.first().parentCollection();
81  if (!std::any_of(items.cbegin(), items.cend(), [parent](const Item &item) {
82  return item.parentCollection() != parent;
83  })) {
84  dragSourceCollection = parent;
85  }
86  }
87 
88  if (dragSourceCollection.isValid()) {
89  // Disable autocommitting, because starting a Link/Unlink/Copy/Move job
90  // after the transaction has ended leaves the job hanging
91  setAutomaticCommittingEnabled(false);
92 
93  auto fetch = new CollectionFetchJob(dragSourceCollection, CollectionFetchJob::Base, this);
94  QObject::connect(fetch, &KJob::finished, this, &PasteHelperJob::onDragSourceCollectionFetched);
95  } else {
96  runActions();
97  }
98 }
99 
100 PasteHelperJob::~PasteHelperJob()
101 {
102 }
103 
104 void PasteHelperJob::onDragSourceCollectionFetched(KJob *job)
105 {
106  auto fetch = qobject_cast<CollectionFetchJob *>(job);
107  qCDebug(AKONADICORE_LOG) << fetch->error() << fetch->collections().count();
108  if (fetch->error() || fetch->collections().count() != 1) {
109  runActions();
110  commit();
111  return;
112  }
113 
114  // If the source collection is virtual, treat copy and move actions differently
115  const Collection sourceCollection = fetch->collections().at(0);
116  qCDebug(AKONADICORE_LOG) << "FROM: " << sourceCollection.id() << sourceCollection.name() << sourceCollection.isVirtual();
117  qCDebug(AKONADICORE_LOG) << "DEST: " << mDestCollection.id() << mDestCollection.name() << mDestCollection.isVirtual();
118  qCDebug(AKONADICORE_LOG) << "ACTN:" << mAction;
119  if (sourceCollection.isVirtual()) {
120  switch (mAction) {
121  case Qt::CopyAction:
122  if (mDestCollection.isVirtual()) {
123  new LinkJob(mDestCollection, mItems, this);
124  } else {
125  new ItemCopyJob(mItems, mDestCollection, this);
126  }
127  break;
128  case Qt::MoveAction:
129  new UnlinkJob(sourceCollection, mItems, this);
130  if (mDestCollection.isVirtual()) {
131  new LinkJob(mDestCollection, mItems, this);
132  } else {
133  new ItemCopyJob(mItems, mDestCollection, this);
134  }
135  break;
136  case Qt::LinkAction:
137  new LinkJob(mDestCollection, mItems, this);
138  break;
139  default:
140  Q_ASSERT(false);
141  }
142  runCollectionsActions();
143  commit();
144  } else {
145  runActions();
146  }
147 
148  commit();
149 }
150 
151 void PasteHelperJob::runActions()
152 {
153  runItemsActions();
154  runCollectionsActions();
155 }
156 
157 void PasteHelperJob::runItemsActions()
158 {
159  if (mItems.isEmpty()) {
160  return;
161  }
162 
163  switch (mAction) {
164  case Qt::CopyAction:
165  new ItemCopyJob(mItems, mDestCollection, this);
166  break;
167  case Qt::MoveAction:
168  new ItemMoveJob(mItems, mDestCollection, this);
169  break;
170  case Qt::LinkAction:
171  new LinkJob(mDestCollection, mItems, this);
172  break;
173  default:
174  Q_ASSERT(false); // WTF?!
175  }
176 }
177 
178 void PasteHelperJob::runCollectionsActions()
179 {
180  if (mCollections.isEmpty()) {
181  return;
182  }
183 
184  switch (mAction) {
185  case Qt::CopyAction:
186  for (const Collection &col : std::as_const(mCollections)) { // FIXME: remove once we have a batch job for collections as well
187  new CollectionCopyJob(col, mDestCollection, this);
188  }
189  break;
190  case Qt::MoveAction:
191  for (const Collection &col : std::as_const(mCollections)) { // FIXME: remove once we have a batch job for collections as well
192  new CollectionMoveJob(col, mDestCollection, this);
193  }
194  break;
195  case Qt::LinkAction:
196  // Not supported for collections
197  break;
198  default:
199  Q_ASSERT(false); // WTF?!
200  }
201 }
202 
203 bool PasteHelper::canPaste(const QMimeData *mimeData, const Collection &collection, Qt::DropAction action)
204 {
205  if (!mimeData || !collection.isValid()) {
206  return false;
207  }
208 
209  // check that the target collection has the rights to
210  // create the pasted items resp. collections
212  if (mimeData->hasUrls()) {
213  const QList<QUrl> urls = mimeData->urls();
214  for (const QUrl &url : urls) {
215  const QUrlQuery query(url);
216  if (query.hasQueryItem(QStringLiteral("item"))) {
217  if (action == Qt::LinkAction) {
218  neededRights |= Collection::CanLinkItem;
219  } else {
220  neededRights |= Collection::CanCreateItem;
221  }
222  } else if (query.hasQueryItem(QStringLiteral("collection"))) {
223  neededRights |= Collection::CanCreateCollection;
224  }
225  }
226 
227  if ((collection.rights() & neededRights) == 0) {
228  return false;
229  }
230 
231  // check that the target collection supports the mime types of the
232  // items/collections that shall be pasted
233  bool supportsMimeTypes = true;
234  for (const QUrl &url : std::as_const(urls)) {
235  const QUrlQuery query(url);
236  // collections do not provide mimetype information, so ignore this check
237  if (query.hasQueryItem(QStringLiteral("collection"))) {
238  continue;
239  }
240 
241  const QString mimeType = query.queryItemValue(QStringLiteral("type"));
242  if (!collection.contentMimeTypes().contains(mimeType)) {
243  supportsMimeTypes = false;
244  break;
245  }
246  }
247 
248  return supportsMimeTypes;
249  }
250 
251  return false;
252 }
253 
254 KJob *PasteHelper::paste(const QMimeData *mimeData, const Collection &collection, Qt::DropAction action, Session *session)
255 {
256  if (!canPaste(mimeData, collection, action)) {
257  return nullptr;
258  }
259 
260  // we try to drop data not coming with the akonadi:// url
261  // find a type the target collection supports
262  const QStringList lstFormats = mimeData->formats();
263  for (const QString &type : lstFormats) {
264  if (!collection.contentMimeTypes().contains(type)) {
265  continue;
266  }
267 
268  QByteArray item = mimeData->data(type);
269  // HACK for some unknown reason the data is sometimes 0-terminated...
270  if (!item.isEmpty() && item.at(item.size() - 1) == 0) {
271  item.resize(item.size() - 1);
272  }
273 
274  Item it;
275  it.setMimeType(type);
276  it.setPayloadFromData(item);
277 
278  auto job = new ItemCreateJob(it, collection);
279  return job;
280  }
281 
282  if (!mimeData->hasUrls()) {
283  return nullptr;
284  }
285 
286  // data contains an url list
287  return pasteUriList(mimeData, collection, action, session);
288 }
289 
290 KJob *PasteHelper::pasteUriList(const QMimeData *mimeData, const Collection &destination, Qt::DropAction action, Session *session)
291 {
292  if (!mimeData->hasUrls()) {
293  return nullptr;
294  }
295 
296  if (!canPaste(mimeData, destination, action)) {
297  return nullptr;
298  }
299 
300  const QList<QUrl> urls = mimeData->urls();
301  Collection::List collections;
302  Item::List items;
303  for (const QUrl &url : urls) {
304  const QUrlQuery query(url);
305  const Collection collection = Collection::fromUrl(url);
306  if (collection.isValid()) {
307  collections.append(collection);
308  }
309  Item item = Item::fromUrl(url);
310  if (query.hasQueryItem(QStringLiteral("parent"))) {
311  item.setParentCollection(Collection(query.queryItemValue(QStringLiteral("parent")).toLongLong()));
312  }
313  if (item.isValid()) {
314  items.append(item);
315  }
316  // TODO: handle non Akonadi URLs?
317  }
318 
319  auto job = new PasteHelperJob(action, items, collections, destination, session);
320 
321  return job;
322 }
323 
324 #include "pastehelper.moc"
bool isValid() const
Returns whether the item is valid.
Definition: item.cpp:88
QByteArray data(const QString &mimeType) const const
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
Definition: collection.cpp:267
Job that unlinks items inside the Akonadi storage.
Definition: unlinkjob.h:51
void finished(KJob *job)
@ CanCreateCollection
Can create new subcollections in this collection.
Definition: collection.h:95
void setPayloadFromData(const QByteArray &data)
Sets the payload based on the canonical representation normally used for data of this mime type.
Definition: item.cpp:301
Job that creates a new item in the Akonadi storage.
Definition: itemcreatejob.h:60
@ CanCreateItem
Can create new items in this collection.
Definition: collection.h:92
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
@ ReadOnly
Can only read items or subcollection of this collection.
Definition: collection.h:90
void append(const T &value)
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
Definition: item.cpp:170
Represents a collection of PIM items.
Definition: collection.h:61
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
void setMimeType(const QString &mimeType)
Sets the mime type of the item to mimeType.
Definition: item.cpp:347
KCALUTILS_EXPORT QString mimeType()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QVector< Item > List
Describes a list of items.
Definition: item.h:116
char at(int i) const const
Job that fetches collections from the Akonadi storage.
Job that moves an item into a different collection in the Akonadi storage.
Definition: itemmovejob.h:34
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
Job that copies a collection into another collection in the Akonadi storage.
static Item fromUrl(const QUrl &url)
Creates an item from the given url.
Definition: item.cpp:391
A communication session with the Akonadi storage.
Definition: core/session.h:55
@ Base
Only fetch the base collection.
Job that moves a collection in the Akonadi storage to a new parent collection.
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
QList< QUrl > urls() const const
DropAction
bool hasUrls() const const
bool isEmpty() const const
void resize(int size)
Job that links items inside the Akonadi storage.
Definition: linkjob.h:51
Job that copies a set of items to a target collection in the Akonadi storage.
Definition: itemcopyjob.h:47
int size() const const
@ CanLinkItem
Can create links to existing items in this virtual collection.
Definition: collection.h:97
virtual QStringList formats() const const
Represents a PIM item stored in Akonadi storage.
Definition: item.h:105
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Jun 7 2023 03:53:31 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.