Akonadi

trashrestorejob.cpp
1 /*
2  * SPDX-FileCopyrightText: 2011 Christian Mollekopf <[email protected]>
3  *
4  * SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 #include "trashrestorejob.h"
8 
9 #include "entitydeletedattribute.h"
10 #include "job_p.h"
11 
12 #include "trashsettings.h"
13 
14 #include <KLocalizedString>
15 
16 #include "collectiondeletejob.h"
17 #include "collectionfetchjob.h"
18 #include "collectionfetchscope.h"
19 #include "collectionmodifyjob.h"
20 #include "collectionmovejob.h"
21 #include "itemdeletejob.h"
22 #include "itemfetchjob.h"
23 #include "itemfetchscope.h"
24 #include "itemmodifyjob.h"
25 #include "itemmovejob.h"
26 
27 #include "akonadicore_debug.h"
28 
29 #include <QHash>
30 
31 using namespace Akonadi;
32 
33 class Akonadi::TrashRestoreJobPrivate : public JobPrivate
34 {
35 public:
36  explicit TrashRestoreJobPrivate(TrashRestoreJob *parent)
37  : JobPrivate(parent)
38  {
39  }
40 
41  void selectResult(KJob *job);
42 
43  // Called when the target collection was fetched,
44  // will issue the move and the removal of the attributes if collection is valid
45  void targetCollectionFetched(KJob *job);
46 
47  void removeAttribute(const Akonadi::Item::List &list);
48  void removeAttribute(const Akonadi::Collection::List &list);
49 
50  // Called after initial fetch of items, issues fetch of target collection or removes attributes for in place restore
51  void itemsReceived(const Akonadi::Item::List &items);
52  void collectionsReceived(const Akonadi::Collection::List &collections);
53 
54  Q_DECLARE_PUBLIC(TrashRestoreJob)
55 
56  Item::List mItems;
57  Collection mCollection;
58  Collection mTargetCollection;
59  QHash<Collection, Item::List> restoreCollections; // groups items to target restore collections
60 };
61 
62 void TrashRestoreJobPrivate::selectResult(KJob *job)
63 {
64  Q_Q(TrashRestoreJob);
65  if (job->error()) {
66  qCWarning(AKONADICORE_LOG) << job->errorString();
67  return; // KCompositeJob takes care of errors
68  }
69 
70  if (!q->hasSubjobs() || (q->subjobs().contains(static_cast<KJob *>(q->sender())) && q->subjobs().size() == 1)) {
71  // qCWarning(AKONADICORE_LOG) << "trash restore finished";
72  q->emitResult();
73  }
74 }
75 
76 void TrashRestoreJobPrivate::targetCollectionFetched(KJob *job)
77 {
78  Q_Q(TrashRestoreJob);
79 
80  auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
81  Q_ASSERT(fetchJob);
82  const Collection::List &list = fetchJob->collections();
83 
84  if (list.isEmpty() || !list.first().isValid() || list.first().hasAttribute<Akonadi::EntityDeletedAttribute>()) { // target collection is invalid/not
85  // existing
86 
87  const QString res = fetchJob->property("Resource").toString();
88  if (res.isEmpty()) { // There is no fallback
89  q->setError(Job::Unknown);
90  q->setErrorText(i18n("Could not find restore collection and restore resource is not available"));
91  q->emitResult();
92  // FAIL
93  qCWarning(AKONADICORE_LOG) << "restore collection not available";
94  return;
95  }
96 
97  // Try again with the root collection of the resource as fallback
99  resRootFetch->fetchScope().setResource(res);
100  const QVariant &var = fetchJob->property("Items");
101  if (var.isValid()) {
102  resRootFetch->setProperty("Items", var.toInt());
103  }
104  q->connect(resRootFetch, &KJob::result, q, [this](KJob *job) {
105  targetCollectionFetched(job);
106  });
107  q->connect(resRootFetch, &KJob::result, q, [this](KJob *job) {
108  selectResult(job);
109  });
110  return;
111  }
112  Q_ASSERT(list.size() == 1);
113  // SUCCESS
114  // We know where to move the entity, so remove the attributes and move them to the right location
115  if (!mItems.isEmpty()) {
116  const QVariant &var = fetchJob->property("Items");
117  Q_ASSERT(var.isValid());
118  const Item::List &items = restoreCollections[Collection(var.toInt())];
119 
120  // store removed attribute if destination collection is valid or the item doesn't have a restore collection
121  // TODO only remove the attribute if the move job was successful (although it is unlikely that it fails since we already fetched the collection)
122  removeAttribute(items);
123  if (items.first().parentCollection() != list.first()) {
124  auto job = new ItemMoveJob(items, list.first(), q);
125  q->connect(job, &KJob::result, q, [this](KJob *job) {
126  selectResult(job);
127  });
128  }
129  } else {
130  Q_ASSERT(mCollection.isValid());
131  // TODO only remove the attribute if the move job was successful
132  removeAttribute(Collection::List() << mCollection);
133  auto collectionFetchJob = new CollectionFetchJob(mCollection, CollectionFetchJob::Recursive, q);
134  q->connect(collectionFetchJob, &KJob::result, q, [this](KJob *job) {
135  selectResult(job);
136  });
137  q->connect(collectionFetchJob, &CollectionFetchJob::collectionsReceived, q, [this](const auto &cols) {
138  removeAttribute(cols);
139  });
140 
141  if (mCollection.parentCollection() != list.first()) {
142  auto job = new CollectionMoveJob(mCollection, list.first(), q);
143  q->connect(job, &KJob::result, q, [this](KJob *job) {
144  selectResult(job);
145  });
146  }
147  }
148 }
149 
150 void TrashRestoreJobPrivate::itemsReceived(const Akonadi::Item::List &items)
151 {
152  Q_Q(TrashRestoreJob);
153  if (items.isEmpty()) {
154  q->setError(Job::Unknown);
155  q->setErrorText(i18n("Invalid items passed"));
156  q->emitResult();
157  return;
158  }
159  mItems = items;
160 
161  // Sort by restore collection
162  for (const Item &item : std::as_const(mItems)) {
163  if (!item.hasAttribute<Akonadi::EntityDeletedAttribute>()) {
164  continue;
165  }
166  // If the restore collection is invalid we restore the item in place, so we don't need to know its restore resource => we can put those cases in the
167  // same list
168  restoreCollections[item.attribute<Akonadi::EntityDeletedAttribute>()->restoreCollection()].append(item);
169  }
170 
171  for (auto it = restoreCollections.cbegin(), e = restoreCollections.cend(); it != e; ++it) {
172  const Item &first = it.value().first();
173  // Move the items to the correct collection if available
174  Collection targetCollection = it.key();
175  const QString restoreResource = first.attribute<Akonadi::EntityDeletedAttribute>()->restoreResource();
176 
177  // Restore in place if no restore collection is set
178  if (!targetCollection.isValid()) {
179  removeAttribute(it.value());
180  return;
181  }
182 
183  // Explicit target overrides the resource
184  if (mTargetCollection.isValid()) {
185  targetCollection = mTargetCollection;
186  }
187 
188  // Try to fetch the target resource to see if it is available
189  auto fetchJob = new CollectionFetchJob(targetCollection, Akonadi::CollectionFetchJob::Base, q);
190  if (!mTargetCollection.isValid()) { // explicit targets don't have a fallback
191  fetchJob->setProperty("Resource", restoreResource);
192  }
193  fetchJob->setProperty("Items", it.key().id()); // to find the items in restore collections again
194  q->connect(fetchJob, &KJob::result, q, [this](KJob *job) {
195  targetCollectionFetched(job);
196  });
197  }
198 }
199 
200 void TrashRestoreJobPrivate::collectionsReceived(const Akonadi::Collection::List &collections)
201 {
202  Q_Q(TrashRestoreJob);
203  if (collections.isEmpty()) {
204  q->setError(Job::Unknown);
205  q->setErrorText(i18n("Invalid collection passed"));
206  q->emitResult();
207  return;
208  }
209  Q_ASSERT(collections.size() == 1);
210  mCollection = collections.first();
211 
212  if (!mCollection.hasAttribute<Akonadi::EntityDeletedAttribute>()) {
213  return;
214  }
215 
216  const QString restoreResource = mCollection.attribute<Akonadi::EntityDeletedAttribute>()->restoreResource();
217  Collection targetCollection = mCollection.attribute<EntityDeletedAttribute>()->restoreCollection();
218 
219  // Restore in place if no restore collection/resource is set
220  if (!targetCollection.isValid()) {
221  removeAttribute(Collection::List() << mCollection);
222  auto collectionFetchJob = new CollectionFetchJob(mCollection, CollectionFetchJob::Recursive, q);
223  q->connect(collectionFetchJob, &KJob::result, q, [this](KJob *job) {
224  selectResult(job);
225  });
226  q->connect(collectionFetchJob, &CollectionFetchJob::collectionsReceived, q, [this](const auto &cols) {
227  removeAttribute(cols);
228  });
229  return;
230  }
231 
232  // Explicit target overrides the resource/configured restore collection
233  if (mTargetCollection.isValid()) {
234  targetCollection = mTargetCollection;
235  }
236 
237  // Fetch the target collection to check if it's valid
238  auto fetchJob = new CollectionFetchJob(targetCollection, CollectionFetchJob::Base, q);
239  if (!mTargetCollection.isValid()) { // explicit targets don't have a fallback
240  fetchJob->setProperty("Resource", restoreResource);
241  }
242  q->connect(fetchJob, &KJob::result, q, [this](KJob *job) {
243  targetCollectionFetched(job);
244  });
245 }
246 
247 void TrashRestoreJobPrivate::removeAttribute(const Akonadi::Collection::List &list)
248 {
249  Q_Q(TrashRestoreJob);
250 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
252 #else
254 #endif
255  while (i.hasNext()) {
256  Collection col = i.next();
258 
259  auto job = new CollectionModifyJob(col, q);
260  q->connect(job, &KJob::result, q, [this](KJob *job) {
261  selectResult(job);
262  });
263 
264  auto itemFetchJob = new ItemFetchJob(col, q);
265  itemFetchJob->fetchScope().fetchAttribute<EntityDeletedAttribute>(true);
266  q->connect(itemFetchJob, &KJob::result, q, [this](KJob *job) {
267  selectResult(job);
268  });
269  q->connect(itemFetchJob, &ItemFetchJob::itemsReceived, q, [this](const auto &items) {
270  removeAttribute(items);
271  });
272  }
273 }
274 
275 void TrashRestoreJobPrivate::removeAttribute(const Akonadi::Item::List &list)
276 {
277  Q_Q(TrashRestoreJob);
278  Item::List items = list;
279 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
281 #else
283 #endif
284  while (i.hasNext()) {
285  Item &item = i.next();
287  auto job = new ItemModifyJob(item, q);
288  job->setIgnorePayload(true);
289  q->connect(job, &KJob::result, q, [this](KJob *job) {
290  selectResult(job);
291  });
292  }
293  // For some reason it is not possible to apply this change to multiple items at once
294  // ItemModifyJob *job = new ItemModifyJob(items, q);
295  // q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
296 }
297 
299  : Job(new TrashRestoreJobPrivate(this), parent)
300 {
302  d->mItems << item;
303 }
304 
306  : Job(new TrashRestoreJobPrivate(this), parent)
307 {
309  d->mItems = items;
310 }
311 
312 TrashRestoreJob::TrashRestoreJob(const Collection &collection, QObject *parent)
313  : Job(new TrashRestoreJobPrivate(this), parent)
314 {
316  d->mCollection = collection;
317 }
318 
319 TrashRestoreJob::~TrashRestoreJob()
320 {
321 }
322 
324 {
326  d->mTargetCollection = collection;
327 }
328 
329 Item::List TrashRestoreJob::items() const
330 {
331  Q_D(const TrashRestoreJob);
332  return d->mItems;
333 }
334 
336 {
338 
339  // We always have to fetch the entities to ensure that the EntityDeletedAttribute is available
340  if (!d->mItems.isEmpty()) {
341  auto job = new ItemFetchJob(d->mItems, this);
342  job->fetchScope().setCacheOnly(true);
343  job->fetchScope().fetchAttribute<EntityDeletedAttribute>(true);
344  connect(job, &ItemFetchJob::itemsReceived, this, [d](const auto &items) {
345  d->itemsReceived(items);
346  });
347  } else if (d->mCollection.isValid()) {
348  auto job = new CollectionFetchJob(d->mCollection, CollectionFetchJob::Base, this);
349  connect(job, &CollectionFetchJob::collectionsReceived, this, [d](const auto &cols) {
350  d->collectionsReceived(cols);
351  });
352  } else {
353  qCWarning(AKONADICORE_LOG) << "No valid collection or empty itemlist";
355  setErrorText(i18n("No valid collection or empty itemlist"));
356  emitResult();
357  }
358 }
359 
360 #include "moc_trashrestorejob.cpp"
T & first()
void itemsReceived(const Akonadi::Item::List &items)
This signal is emitted whenever new items have been fetched completely.
bool isEmpty() const const
bool isValid() const const
Job that modifies a collection in the Akonadi storage.
@ Unknown
Unknown error.
Definition: job.h:102
void setErrorText(const QString &errorText)
void result(KJob *job)
void removeAttribute(const QByteArray &name)
Removes and deletes the attribute of the given type name.
Definition: collection.cpp:156
void append(const T &value)
void setTargetCollection(const Collection &collection)
Sets the target collection, where the item is moved to.
Represents a collection of PIM items.
Definition: collection.h:61
void doStart() override
This method must be reimplemented in the concrete jobs.
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
T & first()
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:115
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
void collectionsReceived(const Akonadi::Collection::List &collections)
This signal is emitted whenever the job has received collections.
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
Definition: collection.cpp:176
int size() const const
Job that modifies an existing item in the Akonadi storage.
Definition: itemmodifyjob.h:81
QString i18n(const char *text, const TYPE &arg...)
TrashRestoreJob(const Item &item, QObject *parent=nullptr)
All items need to be from the same resource.
bool isEmpty() const const
int toInt(bool *ok) const const
bool isEmpty() const const
@ FirstLevel
Only list direct sub-collections of the base collection.
@ Base
Only fetch the base collection.
Base class for all actions in the Akonadi storage.
Definition: job.h:80
Job that moves a collection in the Akonadi storage to a new parent collection.
static Collection root()
Returns the root collection.
Definition: collection.cpp:287
void removeAttribute(const QByteArray &name)
Removes and deletes the attribute of the given type name.
Definition: item.cpp:123
An Attribute that marks that an entity was marked as deleted.
int size() const const
Job that fetches items from the Akonadi storage.
Definition: itemfetchjob.h:69
Attribute * attribute(const QByteArray &name)
Returns the attribute of the given type name if available, 0 otherwise.
Definition: item.cpp:143
@ Recursive
List all sub-collections.
void emitResult()
virtual QString errorString() const
int error() const
Job that restores entities from trash.
void setError(int errorCode)
Represents a PIM item stored in Akonadi storage.
Definition: item.h:104
Q_D(Todo)
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jul 2 2022 06:41:49 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.