Akonadi

pastehelper.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
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
32using namespace Akonadi;
33
34class PasteHelperJob : public Akonadi::TransactionSequence
35{
37
38public:
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
46private Q_SLOTS:
47 void onDragSourceCollectionFetched(KJob *job);
48
49private:
50 void runActions();
51 void runItemsActions();
52 void runCollectionsActions();
53
54private:
56 Akonadi::Collection::List mCollections;
57 Akonadi::Collection mDestCollection;
58 Qt::DropAction mAction;
59};
60
61PasteHelperJob::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
100PasteHelperJob::~PasteHelperJob()
101{
102}
103
104void 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
151void PasteHelperJob::runActions()
152{
153 runItemsActions();
154 runCollectionsActions();
155}
156
157void 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
178void 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
203bool 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
254KJob *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
290KJob *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"
Job that copies a collection into another collection in the Akonadi storage.
Job that fetches collections from the Akonadi storage.
@ Base
Only fetch the base collection.
Job that moves a collection in the Akonadi storage to a new parent collection.
Represents a collection of PIM items.
Definition collection.h:62
static Collection fromUrl(const QUrl &url)
Creates a collection from the given url.
@ ReadOnly
Can only read items or subcollection of this collection.
Definition collection.h:90
@ CanCreateItem
Can create new items in this collection.
Definition collection.h:92
@ CanLinkItem
Can create links to existing items in this virtual collection.
Definition collection.h:97
@ CanCreateCollection
Can create new subcollections in this collection.
Definition collection.h:95
Job that copies a set of items to a target collection in the Akonadi storage.
Definition itemcopyjob.h:48
Job that creates a new item in the Akonadi storage.
Job that moves an item into a different collection in the Akonadi storage.
Definition itemmovejob.h:35
Collection parentCollection() const
Returns the parent collection of this object.
Definition item.cpp:161
Job that links items inside the Akonadi storage.
Definition linkjob.h:52
Base class for jobs that need to run a sequence of sub-jobs in a transaction.
void commit()
Commits the transaction as soon as all pending sub-jobs finished successfully.
Job that unlinks items inside the Akonadi storage.
Definition unlinkjob.h:52
void finished(KJob *job)
ASAP CLI session.
Helper integration between Akonadi and Qt.
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KCALUTILS_EXPORT QString mimeType()
QCA_EXPORT void setProperty(const QString &name, const QVariant &value)
char at(qsizetype i) const const
bool isEmpty() const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
void append(QList< T > &&value)
const_iterator cbegin() const const
const_iterator cend() const const
T & first()
bool isEmpty() const const
QByteArray data(const QString &mimeType) const const
virtual QStringList formats() const const
bool hasUrls() const const
QList< QUrl > urls() const const
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
T qobject_cast(QObject *object)
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
DropAction
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:29 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.