Akonadi

trashjob.cpp
1/*
2 SPDX-FileCopyrightText: 2011 Christian Mollekopf <chrigi_1@fastmail.fm>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "trashjob.h"
8
9#include "entitydeletedattribute.h"
10#include "job_p.h"
11#include "trashsettings.h"
12
13#include <KLocalizedString>
14
15#include "collectiondeletejob.h"
16#include "collectionfetchjob.h"
17#include "collectionfetchscope.h"
18#include "collectionmodifyjob.h"
19#include "collectionmovejob.h"
20#include "itemdeletejob.h"
21#include "itemfetchjob.h"
22#include "itemfetchscope.h"
23#include "itemmodifyjob.h"
24#include "itemmovejob.h"
25
26#include "akonadicore_debug.h"
27
28#include <QHash>
29
30using namespace Akonadi;
31
32class Akonadi::TrashJobPrivate : public JobPrivate
33{
34public:
35 explicit TrashJobPrivate(TrashJob *parent)
36 : JobPrivate(parent)
37 {
38 }
39 // 4.
40 void selectResult(KJob *job);
41 // 3.
42 // Helper functions to recursively set the attribute on deleted collections
43 void setAttribute(const Akonadi::Collection::List & /*list*/);
44 void setAttribute(const Akonadi::Item::List & /*list*/);
45 // Set attributes after ensuring that move job was successful
46 void setAttribute(KJob *job);
47
48 // 2.
49 // called after parent of the trashed item was fetched (needed to see in which resource the item is in)
50 void parentCollectionReceived(const Akonadi::Collection::List & /*collections*/);
51
52 // 1.
53 // called after initial fetch of trashed items
54 void itemsReceived(const Akonadi::Item::List & /*items*/);
55 // called after initial fetch of trashed collection
56 void collectionsReceived(const Akonadi::Collection::List & /*collections*/);
57
58 Q_DECLARE_PUBLIC(TrashJob)
59
60 Item::List mItems;
61 Collection mCollection;
62 Collection mRestoreCollection;
63 Collection mTrashCollection;
64 bool mKeepTrashInCollection = false;
65 bool mSetRestoreCollection = false; // only set restore collection when moved to trash collection (not in place)
66 bool mDeleteIfInTrash = false;
67 QHash<Collection, Item::List> mCollectionItems; // list of trashed items sorted according to parent collection
68 QHash<Item::Id, Collection> mParentCollections; // fetched parent collection of items (containing the resource name)
69};
70
71void TrashJobPrivate::selectResult(KJob *job)
72{
73 Q_Q(TrashJob);
74 if (job->error()) {
75 qCWarning(AKONADICORE_LOG) << job->objectName();
76 qCWarning(AKONADICORE_LOG) << job->errorString();
77 return; // KCompositeJob takes care of errors
78 }
79
80 if (!q->hasSubjobs() || (q->subjobs().contains(static_cast<KJob *>(q->sender())) && q->subjobs().size() == 1)) {
81 q->emitResult();
82 }
83}
84
85void TrashJobPrivate::setAttribute(const Akonadi::Collection::List &list)
86{
87 Q_Q(TrashJob);
89 while (i.hasNext()) {
90 const Collection &col = i.next();
91 auto eda = new EntityDeletedAttribute();
92 if (mSetRestoreCollection) {
93 Q_ASSERT(mRestoreCollection.isValid());
94 eda->setRestoreCollection(mRestoreCollection);
95 }
96
97 Collection modCol(col.id()); // really only modify attribute (forget old remote ids, etc.), otherwise we have an error because of the move
98 modCol.addAttribute(eda);
99
100 auto job = new CollectionModifyJob(modCol, q);
101 q->connect(job, &KJob::result, q, [this](KJob *job) {
102 selectResult(job);
103 });
104
105 auto itemFetchJob = new ItemFetchJob(col, q);
106 // TODO not sure if it is guaranteed that itemsReceived is always before result (otherwise the result is emitted before the attributes are set)
107 q->connect(itemFetchJob, &ItemFetchJob::itemsReceived, q, [this](const auto &items) {
108 setAttribute(items);
109 });
110 q->connect(itemFetchJob, &KJob::result, q, [this](KJob *job) {
111 selectResult(job);
112 });
113 }
114}
115
116void TrashJobPrivate::setAttribute(const Akonadi::Item::List &list)
117{
118 Q_Q(TrashJob);
119 Item::List items = list;
121 while (i.hasNext()) {
122 const Item &item = i.next();
123 auto eda = new EntityDeletedAttribute();
124 if (mSetRestoreCollection) {
125 // When deleting a collection, we want to restore the deleted collection's items restored to the deleted collection's parent, not the items parent
126 if (mRestoreCollection.isValid()) {
127 eda->setRestoreCollection(mRestoreCollection);
128 } else {
129 Q_ASSERT(mParentCollections.contains(item.parentCollection().id()));
130 eda->setRestoreCollection(mParentCollections.value(item.parentCollection().id()));
131 }
132 }
133
134 Item modItem(item.id()); // really only modify attribute (forget old remote ids, etc.)
135 modItem.addAttribute(eda);
136 auto job = new ItemModifyJob(modItem, q);
137 job->setIgnorePayload(true);
138 q->connect(job, &KJob::result, q, [this](KJob *job) {
139 selectResult(job);
140 });
141 }
142
143 // For some reason it is not possible to apply this change to multiple items at once
144 /*ItemModifyJob *job = new ItemModifyJob(items, q);
145 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );*/
146}
147
148void TrashJobPrivate::setAttribute(KJob *job)
149{
150 Q_Q(TrashJob);
151 if (job->error()) {
152 qCWarning(AKONADICORE_LOG) << job->objectName();
153 qCWarning(AKONADICORE_LOG) << job->errorString();
154 q->setError(Job::Unknown);
155 q->setErrorText(i18n("Move to trash collection failed, aborting trash operation"));
156 return;
157 }
158
159 // For Items
160 const QVariant var = job->property("MovedItems");
161 if (var.isValid()) {
162 int id = var.toInt();
163 Q_ASSERT(id >= 0);
164 setAttribute(mCollectionItems.value(Collection(id)));
165 return;
166 }
167
168 // For a collection
169 Q_ASSERT(mCollection.isValid());
170 setAttribute(Collection::List() << mCollection);
171 // Set the attribute on all subcollections and items
172 auto colFetchJob = new CollectionFetchJob(mCollection, CollectionFetchJob::Recursive, q);
173 q->connect(colFetchJob, &CollectionFetchJob::collectionsReceived, q, [this](const auto &cols) {
174 setAttribute(cols);
175 });
176 q->connect(colFetchJob, &KJob::result, q, [this](KJob *job) {
177 selectResult(job);
178 });
179}
180
181void TrashJobPrivate::parentCollectionReceived(const Akonadi::Collection::List &collections)
182{
183 Q_Q(TrashJob);
184 Q_ASSERT(collections.size() == 1);
185 const Collection &parentCollection = collections.first();
186
187 // store attribute
188 Q_ASSERT(!parentCollection.resource().isEmpty());
189 Collection trashCollection = mTrashCollection;
190 if (!mTrashCollection.isValid()) {
191 trashCollection = TrashSettings::getTrashCollection(parentCollection.resource());
192 }
193 if (!mKeepTrashInCollection && trashCollection.isValid()) { // Only set the restore collection if the item is moved to trash
194 mSetRestoreCollection = true;
195 }
196
197 mParentCollections.insert(parentCollection.id(), parentCollection);
198
199 if (trashCollection.isValid()) { // Move the items to the correct collection if available
200 auto job = new ItemMoveJob(mCollectionItems.value(parentCollection), trashCollection, q);
201 job->setProperty("MovedItems", parentCollection.id());
202 q->connect(job, &KJob::result, q, [this](KJob *job) {
203 setAttribute(job);
204 }); // Wait until the move finished to set the attribute
205 q->connect(job, &KJob::result, q, [this](KJob *job) {
206 selectResult(job);
207 });
208 } else {
209 setAttribute(mCollectionItems.value(parentCollection));
210 }
211}
212
213void TrashJobPrivate::itemsReceived(const Akonadi::Item::List &items)
214{
215 Q_Q(TrashJob);
216 if (items.isEmpty()) {
217 q->setError(Job::Unknown);
218 q->setErrorText(i18n("Invalid items passed"));
219 q->emitResult();
220 return;
221 }
222
223 Item::List toDelete;
224 QListIterator<Item> i(items);
225 while (i.hasNext()) {
226 const Item &item = i.next();
227 if (item.hasAttribute<EntityDeletedAttribute>()) {
228 toDelete.append(item);
229 continue;
230 }
231 Q_ASSERT(item.parentCollection().isValid());
232 mCollectionItems[item.parentCollection()].append(item); // Sort by parent col ( = restore collection)
233 }
234
235 for (auto it = mCollectionItems.cbegin(), e = mCollectionItems.cend(); it != e; ++it) {
236 auto job = new CollectionFetchJob(it.key(), Akonadi::CollectionFetchJob::Base, q);
237 q->connect(job, &CollectionFetchJob::collectionsReceived, q, [this](const auto &cols) {
238 parentCollectionReceived(cols);
239 });
240 }
241
242 if (mDeleteIfInTrash && !toDelete.isEmpty()) {
243 auto job = new ItemDeleteJob(toDelete, q);
244 q->connect(job, &KJob::result, q, [this](KJob *job) {
245 selectResult(job);
246 });
247 } else if (mCollectionItems.isEmpty()) { // No job started, so we abort the job
248 qCWarning(AKONADICORE_LOG) << "Nothing to do";
249 q->emitResult();
250 }
251}
252
253void TrashJobPrivate::collectionsReceived(const Akonadi::Collection::List &collections)
254{
255 Q_Q(TrashJob);
256 if (collections.isEmpty()) {
257 q->setError(Job::Unknown);
258 q->setErrorText(i18n("Invalid collection passed"));
259 q->emitResult();
260 return;
261 }
262 Q_ASSERT(collections.size() == 1);
263 mCollection = collections.first();
264
265 if (mCollection.hasAttribute<EntityDeletedAttribute>()) { // marked as deleted
266 if (mDeleteIfInTrash) {
267 auto job = new CollectionDeleteJob(mCollection, q);
268 q->connect(job, &KJob::result, q, [this](KJob *job) {
269 selectResult(job);
270 });
271 } else {
272 qCWarning(AKONADICORE_LOG) << "Nothing to do";
273 q->emitResult();
274 }
275 return;
276 }
277
278 Collection trashCollection = mTrashCollection;
279 if (!mTrashCollection.isValid()) {
280 trashCollection = TrashSettings::getTrashCollection(mCollection.resource());
281 }
282 if (!mKeepTrashInCollection && trashCollection.isValid()) { // only set the restore collection if the item is moved to trash
283 mSetRestoreCollection = true;
284 Q_ASSERT(mCollection.parentCollection().isValid());
285 mRestoreCollection = mCollection.parentCollection();
286 mRestoreCollection.setResource(mCollection.resource()); // The parent collection doesn't contain the resource, so we have to set it manually
287 }
288
289 if (trashCollection.isValid()) {
290 auto job = new CollectionMoveJob(mCollection, trashCollection, q);
291 q->connect(job, &KJob::result, q, [this](KJob *job) {
292 setAttribute(job);
293 });
294 q->connect(job, &KJob::result, q, [this](KJob *job) {
295 selectResult(job);
296 });
297 } else {
298 setAttribute(Collection::List() << mCollection);
299 }
300}
301
302TrashJob::TrashJob(const Item &item, QObject *parent)
303 : Job(new TrashJobPrivate(this), parent)
304{
305 Q_D(TrashJob);
306 d->mItems << item;
307}
308
310 : Job(new TrashJobPrivate(this), parent)
311{
312 Q_D(TrashJob);
313 d->mItems = items;
314}
315
316TrashJob::TrashJob(const Collection &collection, QObject *parent)
317 : Job(new TrashJobPrivate(this), parent)
318{
319 Q_D(TrashJob);
320 d->mCollection = collection;
321}
322
323TrashJob::~TrashJob()
324{
325}
326
327Item::List TrashJob::items() const
328{
329 Q_D(const TrashJob);
330 return d->mItems;
331}
332
334{
335 Q_D(TrashJob);
336 d->mTrashCollection = collection;
337}
338
340{
341 Q_D(TrashJob);
342 d->mKeepTrashInCollection = enable;
343}
344
346{
347 Q_D(TrashJob);
348 d->mDeleteIfInTrash = enable;
349}
350
352{
353 Q_D(TrashJob);
354
355 // Fetch items first to ensure that the EntityDeletedAttribute is available
356 if (!d->mItems.isEmpty()) {
357 auto job = new ItemFetchJob(d->mItems, this);
358 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); // so we have access to the resource
359 // job->fetchScope().setCacheOnly(true);
360 job->fetchScope().fetchAttribute<EntityDeletedAttribute>(true);
361 connect(job, &ItemFetchJob::itemsReceived, this, [d](const auto &items) {
362 d->itemsReceived(items);
363 });
364
365 } else if (d->mCollection.isValid()) {
366 auto job = new CollectionFetchJob(d->mCollection, CollectionFetchJob::Base, this);
367 job->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::Parent);
368 connect(job, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) {
369 d->collectionsReceived(cols);
370 });
371
372 } else {
373 qCWarning(AKONADICORE_LOG) << "No valid collection or empty itemlist";
375 setErrorText(i18n("No valid collection or empty itemlist"));
376 emitResult();
377 }
378}
379
380#include "moc_trashjob.cpp"
Job that deletes a collection in the Akonadi storage.
Job that fetches collections from the Akonadi storage.
@ Recursive
List all sub-collections.
@ Base
Only fetch the base collection.
void collectionsReceived(const Akonadi::Collection::List &collections)
This signal is emitted whenever the job has received collections.
@ Parent
Only retrieve the immediate parent collection.
Job that modifies a collection in the Akonadi storage.
Job that moves a collection in the Akonadi storage to a new parent collection.
Represents a collection of PIM items.
Definition collection.h:62
bool hasAttribute(const QByteArray &name) const
Returns true if the collection has an attribute of the given type name, false otherwise.
void setResource(const QString &identifier)
Sets the identifier of the resource owning the collection.
Collection parentCollection() const
Returns the parent collection of this object.
An Attribute that marks that an entity was marked as deleted.
Job that deletes items from the Akonadi storage.
Job that fetches items from the Akonadi storage.
void itemsReceived(const Akonadi::Item::List &items)
This signal is emitted whenever new items have been fetched completely.
@ Parent
Only retrieve the immediate parent collection.
Job that modifies an existing item in the Akonadi storage.
Job that moves an item into a different collection in the Akonadi storage.
Definition itemmovejob.h:35
Represents a PIM item stored in Akonadi storage.
Definition item.h:101
Base class for all actions in the Akonadi storage.
Definition job.h:81
@ Unknown
Unknown error.
Definition job.h:102
Job that moves items/collection to trash.
Definition trashjob.h:53
void deleteIfInTrash(bool enable)
Delete Items which are already in trash, instead of ignoring them.
Definition trashjob.cpp:345
void doStart() override
This method must be reimplemented in the concrete jobs.
Definition trashjob.cpp:351
void setTrashCollection(const Collection &trashcollection)
Moves all entities to the give collection.
Definition trashjob.cpp:333
void keepTrashInCollection(bool enable)
Ignore configured Trash collections and keep all items local.
Definition trashjob.cpp:339
TrashJob(const Item &item, QObject *parent=nullptr)
Creates a new trash job that marks item as trash, and moves it to the configured trash collection.
Definition trashjob.cpp:302
void setErrorText(const QString &errorText)
virtual QString errorString() const
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
QString i18n(const char *text, const TYPE &arg...)
AKONADICORE_EXPORT Collection getTrashCollection(const QString &resource)
Get the trash collection for the given resource.
Helper integration between Akonadi and Qt.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void append(QList< T > &&value)
T & first()
bool isEmpty() const const
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QVariant property(const char *name) const const
T qobject_cast(QObject *object)
bool setProperty(const char *name, QVariant &&value)
bool isEmpty() const const
bool isValid() const const
int toInt(bool *ok) const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.