Akonadi

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

KDE's Doxygen guidelines are available online.