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

KDE's Doxygen guidelines are available online.