Akonadi

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

KDE's Doxygen guidelines are available online.