2
Copyright (c) 2011 Christian Mollekopf <chrigi_1@fastmail.fm>
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.
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.
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
22
#include "collection.h"
23
#include "entitydeletedattribute.h"
26
#include "trashsettings.h"
30
#include <akonadi/itemdeletejob.h>
31
#include <akonadi/collectiondeletejob.h>
32
#include <akonadi/itemmovejob.h>
33
#include <akonadi/collectionmovejob.h>
34
#include <akonadi/itemmodifyjob.h>
35
#include <akonadi/collectionmodifyjob.h>
36
#include <akonadi/itemfetchscope.h>
37
#include <akonadi/collectionfetchscope.h>
38
#include <akonadi/itemfetchjob.h>
39
#include <akonadi/collectionfetchjob.h>
43
using namespace Akonadi;
45
class TrashJob::TrashJobPrivate : public JobPrivate
48
TrashJobPrivate( TrashJob *parent )
49
: JobPrivate( parent ),
50
mKeepTrashInCollection( false ),
51
mSetRestoreCollection( false ),
52
mDeleteIfInTrash( false ) {
55
void selectResult( KJob *job );
57
//Helper functions to recursivly set the attribute on deleted collections
58
void setAttribute( const Akonadi::Collection::List & );
59
void setAttribute( const Akonadi::Item::List & );
60
//Set attributes after ensuring that move job was successful
61
void setAttribute( KJob *job );
64
//called after parent of the trashed item was fetched (needed to see in which resource the item is in)
65
void parentCollectionReceived( const Akonadi::Collection::List & );
69
//called after initial fetch of trashed items
70
void itemsReceived( const Akonadi::Item::List & );
71
//called after initial fetch of trashed collection
72
void collectionsReceived( const Akonadi::Collection::List & );
75
Q_DECLARE_PUBLIC( TrashJob )
78
Collection mCollection;
79
Collection mRestoreCollection;
80
Collection mTrashCollection;
81
bool mKeepTrashInCollection;
82
bool mSetRestoreCollection; //only set restore collection when moved to trash collection (not in place)
83
bool mDeleteIfInTrash;
84
QHash<Collection, Item::List> mCollectionItems; //list of trashed items sorted according to parent collection
85
QHash<Entity::Id, Collection> mParentCollections; //fetched parent collcetion of items (containing the resource name)
89
void TrashJob::TrashJobPrivate::selectResult( KJob *job )
93
kWarning() << job->objectName();
94
kWarning() << job->errorString();
95
return; // KCompositeJob takes care of errors
98
if ( !q->hasSubjobs() || ( q->subjobs().contains( static_cast<KJob*>( q->sender() ) ) && q->subjobs().size() == 1 ) ) {
103
void TrashJob::TrashJobPrivate::setAttribute( const Akonadi::Collection::List &list )
106
QListIterator<Collection> i( list );
107
while ( i.hasNext() ) {
108
const Collection &col = i.next();
109
EntityDeletedAttribute *eda = new EntityDeletedAttribute();
110
if ( mSetRestoreCollection ) {
111
Q_ASSERT( mRestoreCollection.isValid() );
112
eda->setRestoreCollection( mRestoreCollection );
115
Collection modCol( col.id() ); //really only modify attribute (forget old remote ids, etc.), otherwise we have an error because of the move
116
modCol.addAttribute( eda );
118
CollectionModifyJob *job = new CollectionModifyJob( modCol, q );
119
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
121
ItemFetchJob *itemFetchJob = new ItemFetchJob( col, q );
122
//TODO not sure if it is guaranteed that itemsReceived is always before result (otherwise the result is emitted before the attributes are set)
123
q->connect( itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(setAttribute(Akonadi::Item::List)) );
124
q->connect( itemFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
128
void TrashJob::TrashJobPrivate::setAttribute( const Akonadi::Item::List &list )
131
Item::List items = list;
132
QMutableListIterator<Item> i( items );
133
while ( i.hasNext() ) {
134
const Item &item = i.next();
135
EntityDeletedAttribute *eda = new EntityDeletedAttribute();
136
if ( mSetRestoreCollection ) {
137
//When deleting a collection, we want to restore the deleted collection's items restored to the deleted collection's parent, not the items parent
138
if ( mRestoreCollection.isValid() ) {
139
eda->setRestoreCollection( mRestoreCollection );
141
Q_ASSERT( mParentCollections.contains( item.parentCollection().id() ) );
142
eda->setRestoreCollection( mParentCollections.value( item.parentCollection().id() ) );
146
Item modItem( item.id() ); //really only modify attribute (forget old remote ids, etc.)
147
modItem.addAttribute( eda );
148
ItemModifyJob *job = new ItemModifyJob( modItem, q );
149
job->setIgnorePayload( true );
150
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
153
//For some reason it is not possible to apply this change to multiple items at once
154
/*ItemModifyJob *job = new ItemModifyJob(items, q);
155
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );*/
158
void TrashJob::TrashJobPrivate::setAttribute( KJob* job )
161
if ( job->error() ) {
162
kWarning() << job->objectName();
163
kWarning() << job->errorString();
164
q->setError( Job::Unknown );
165
q->setErrorText( i18n( "Move to trash collection failed, aborting trash operation" ) );
170
const QVariant var = job->property( "MovedItems" );
171
if ( var.isValid() ) {
172
int id = var.toInt();
174
setAttribute( mCollectionItems.value( Collection( id ) ) );
179
Q_ASSERT( mCollection.isValid() );
180
setAttribute( Collection::List() << mCollection );
181
//Set the attribute on all subcollections and items
182
CollectionFetchJob *colFetchJob = new CollectionFetchJob( mCollection, CollectionFetchJob::Recursive, q );
183
q->connect( colFetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(setAttribute(Akonadi::Collection::List)) );
184
q->connect( colFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
187
void TrashJob::TrashJobPrivate::parentCollectionReceived( const Akonadi::Collection::List &collections )
190
Q_ASSERT( collections.size() == 1 );
191
const Collection &parentCollection = collections.first();
194
Q_ASSERT( !parentCollection.resource().isEmpty() );
195
Collection trashCollection = mTrashCollection;
196
if ( !mTrashCollection.isValid() ) {
197
trashCollection = TrashSettings::getTrashCollection( parentCollection.resource() );
199
if ( !mKeepTrashInCollection && trashCollection.isValid() ) { //Only set the restore collection if the item is moved to trash
200
mSetRestoreCollection = true;
203
mParentCollections.insert( parentCollection.id(), parentCollection );
205
if ( trashCollection.isValid() ) { //Move the items to the correct collection if available
206
ItemMoveJob *job = new ItemMoveJob( mCollectionItems.value( parentCollection ), trashCollection, q );
207
job->setProperty( "MovedItems", parentCollection.id() );
208
q->connect( job, SIGNAL(result(KJob*)), SLOT(setAttribute(KJob*)) ); //Wait until the move finished to set the attirbute
209
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
211
setAttribute( mCollectionItems.value( parentCollection ) );
215
void TrashJob::TrashJobPrivate::itemsReceived( const Akonadi::Item::List &items )
218
if ( items.isEmpty() ) {
219
q->setError( Job::Unknown );
220
q->setErrorText( i18n( "Invalid items passed" ) );
227
QListIterator<Item> i( items );
228
while ( i.hasNext() ) {
229
const Item &item = i.next();
230
if ( item.hasAttribute<EntityDeletedAttribute>() ) {
231
toDelete.append( item );
234
Q_ASSERT( item.parentCollection().isValid() );
235
mCollectionItems[item.parentCollection()].append( item ); //Sort by parent col ( = restore collection)
238
foreach( const Collection &col, mCollectionItems.keys() ) { //krazy:exclude=foreach
239
CollectionFetchJob *job = new CollectionFetchJob( col, Akonadi::CollectionFetchJob::Base, q );
240
q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
241
SLOT(parentCollectionReceived(Akonadi::Collection::List)) );
244
if ( mDeleteIfInTrash && !toDelete.isEmpty() ) {
245
ItemDeleteJob *job = new ItemDeleteJob( toDelete, q );
246
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
247
} else if ( mCollectionItems.isEmpty() ) { //No job started, so we abort the job
248
kWarning() << "Nothing to do";
254
void TrashJob::TrashJobPrivate::collectionsReceived( const Akonadi::Collection::List &collections )
257
if ( collections.isEmpty() ) {
258
q->setError( Job::Unknown );
259
q->setErrorText( i18n( "Invalid collection passed" ) );
263
Q_ASSERT( collections.size() == 1 );
264
mCollection = collections.first();
266
if ( mCollection.hasAttribute<EntityDeletedAttribute>() ) {//marked as deleted
267
if ( mDeleteIfInTrash ) {
268
CollectionDeleteJob *job = new CollectionDeleteJob( mCollection, q );
269
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
271
kWarning() << "Nothing to do";
277
Collection trashCollection = mTrashCollection;
278
if ( !mTrashCollection.isValid() ) {
279
trashCollection = TrashSettings::getTrashCollection( mCollection.resource() );
281
if ( !mKeepTrashInCollection && trashCollection.isValid() ) { //only set the restore collection if the item is moved to trash
282
mSetRestoreCollection = true;
283
Q_ASSERT( mCollection.parentCollection().isValid() );
284
mRestoreCollection = mCollection.parentCollection();
285
mRestoreCollection.setResource( mCollection.resource() ); //The parent collection doesn't contain the resource, so we have to set it manually
288
if ( trashCollection.isValid() ) {
289
CollectionMoveJob *job = new CollectionMoveJob( mCollection, trashCollection, q );
290
q->connect( job, SIGNAL(result(KJob*)), SLOT(setAttribute(KJob*)) );
291
q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
293
setAttribute( Collection::List() << mCollection );
301
TrashJob::TrashJob( const Item & item, QObject * parent )
302
: Job( new TrashJobPrivate( this ), parent )
308
TrashJob::TrashJob( const Item::List& items, QObject* parent )
309
: Job( new TrashJobPrivate( this ), parent )
315
TrashJob::TrashJob( const Collection& collection, QObject* parent )
316
: Job( new TrashJobPrivate( this ), parent )
319
d->mCollection = collection;
322
TrashJob::~TrashJob()
326
Item::List TrashJob::items() const
328
Q_D( const TrashJob );
332
void TrashJob::setTrashCollection( const Akonadi::Collection &collection )
335
d->mTrashCollection = collection;
338
void TrashJob::keepTrashInCollection( bool enable )
341
d->mKeepTrashInCollection = enable;
344
void TrashJob::deleteIfInTrash( bool enable )
347
d->mDeleteIfInTrash = enable;
350
void TrashJob::doStart()
354
//Fetch items first to ensure that the EntityDeletedAttribute is available
355
if ( !d->mItems.isEmpty() ) {
356
ItemFetchJob *job = new ItemFetchJob( d->mItems, this );
357
job->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); //so we have access to the resource
358
//job->fetchScope().setCacheOnly(true);
359
job->fetchScope().fetchAttribute<EntityDeletedAttribute>( true );
360
connect( job, SIGNAL(itemsReceived(Akonadi::Item::List)), this, SLOT(itemsReceived(Akonadi::Item::List)) );
362
} else if ( d->mCollection.isValid() ) {
363
CollectionFetchJob *job = new CollectionFetchJob( d->mCollection, CollectionFetchJob::Base, this );
364
job->fetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::Parent );
365
connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(collectionsReceived(Akonadi::Collection::List)) );
368
kWarning() << "No valid collection or empty itemlist";
369
setError( Job::Unknown );
370
setErrorText( i18n( "No valid collection or empty itemlist" ) );
375
#include "trashjob.moc"