~ubuntu-branches/ubuntu/raring/kdepimlibs/raring-proposed

« back to all changes in this revision

Viewing changes to akonadi/trashjob.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2011-12-14 14:37:07 UTC
  • mfrom: (1.1.72)
  • Revision ID: package-import@ubuntu.com-20111214143707-nvfc00wnfayzn9ig
Tags: 4:4.7.90-0ubuntu1
* New upstream beta release
* Add packages libkalarmcal2 and libakonadi-notes4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    Copyright (c) 2011 Christian Mollekopf <chrigi_1@fastmail.fm>
 
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 <KLocale>
 
29
 
 
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>
 
40
 
 
41
#include <QHash>
 
42
 
 
43
using namespace Akonadi;
 
44
 
 
45
class TrashJob::TrashJobPrivate : public JobPrivate
 
46
{
 
47
  public:
 
48
    TrashJobPrivate( TrashJob *parent )
 
49
        : JobPrivate( parent ),
 
50
        mKeepTrashInCollection( false ),
 
51
        mSetRestoreCollection( false ),
 
52
        mDeleteIfInTrash( false ) {
 
53
    }
 
54
//4.
 
55
    void selectResult( KJob *job );
 
56
//3.
 
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 );
 
62
 
 
63
//2.
 
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 & );
 
66
 
 
67
 
 
68
//1.
 
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 & );
 
73
 
 
74
 
 
75
    Q_DECLARE_PUBLIC( TrashJob )
 
76
 
 
77
    Item::List mItems;
 
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)
 
86
 
 
87
};
 
88
 
 
89
void TrashJob::TrashJobPrivate::selectResult( KJob *job )
 
90
{
 
91
  Q_Q( TrashJob );
 
92
  if ( job->error() ) {
 
93
    kWarning() << job->objectName();
 
94
    kWarning() << job->errorString();
 
95
    return; // KCompositeJob takes care of errors
 
96
  }
 
97
 
 
98
  if ( !q->hasSubjobs() || ( q->subjobs().contains( static_cast<KJob*>( q->sender() ) ) && q->subjobs().size() == 1 ) ) {
 
99
    q->emitResult();
 
100
  }
 
101
}
 
102
 
 
103
void TrashJob::TrashJobPrivate::setAttribute( const Akonadi::Collection::List &list )
 
104
{
 
105
  Q_Q( TrashJob );
 
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 );
 
113
    }
 
114
 
 
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 );
 
117
 
 
118
    CollectionModifyJob *job = new CollectionModifyJob( modCol, q );
 
119
    q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );
 
120
 
 
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*)) );
 
125
  }
 
126
}
 
127
 
 
128
void TrashJob::TrashJobPrivate::setAttribute( const Akonadi::Item::List &list )
 
129
{
 
130
  Q_Q( TrashJob );
 
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 );
 
140
      } else {
 
141
        Q_ASSERT( mParentCollections.contains( item.parentCollection().id() ) );
 
142
        eda->setRestoreCollection( mParentCollections.value( item.parentCollection().id() ) );
 
143
      }
 
144
    }
 
145
 
 
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*)) );
 
151
  }
 
152
 
 
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*)) );*/
 
156
}
 
157
 
 
158
void TrashJob::TrashJobPrivate::setAttribute( KJob* job )
 
159
{
 
160
  Q_Q( TrashJob );
 
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" ) );
 
166
    return;
 
167
  }
 
168
 
 
169
  //For Items
 
170
  const QVariant var = job->property( "MovedItems" );
 
171
  if ( var.isValid() ) {
 
172
    int id = var.toInt();
 
173
    Q_ASSERT( id >= 0 );
 
174
    setAttribute( mCollectionItems.value( Collection( id ) ) );
 
175
    return;
 
176
  }
 
177
 
 
178
  //For a collection
 
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*)) );
 
185
}
 
186
 
 
187
void TrashJob::TrashJobPrivate::parentCollectionReceived( const Akonadi::Collection::List &collections )
 
188
{
 
189
  Q_Q( TrashJob );
 
190
  Q_ASSERT( collections.size() == 1 );
 
191
  const Collection &parentCollection = collections.first();
 
192
 
 
193
  //store attribute
 
194
  Q_ASSERT( !parentCollection.resource().isEmpty() );
 
195
  Collection trashCollection = mTrashCollection;
 
196
  if ( !mTrashCollection.isValid() ) {
 
197
    trashCollection = TrashSettings::getTrashCollection( parentCollection.resource() );
 
198
  }
 
199
  if ( !mKeepTrashInCollection && trashCollection.isValid() ) { //Only set the restore collection if the item is moved to trash
 
200
    mSetRestoreCollection = true;
 
201
  }
 
202
 
 
203
  mParentCollections.insert( parentCollection.id(), parentCollection );
 
204
 
 
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*)) );
 
210
  } else {
 
211
    setAttribute( mCollectionItems.value( parentCollection ) );
 
212
  }
 
213
}
 
214
 
 
215
void TrashJob::TrashJobPrivate::itemsReceived( const Akonadi::Item::List &items )
 
216
{
 
217
  Q_Q( TrashJob );
 
218
  if ( items.isEmpty() ) {
 
219
    q->setError( Job::Unknown );
 
220
    q->setErrorText( i18n( "Invalid items passed" ) );
 
221
    q->emitResult();
 
222
    return;
 
223
  }
 
224
 
 
225
  Item::List toDelete;
 
226
 
 
227
  QListIterator<Item> i( items );
 
228
  while ( i.hasNext() ) {
 
229
    const Item &item = i.next();
 
230
    if ( item.hasAttribute<EntityDeletedAttribute>() ) {
 
231
      toDelete.append( item );
 
232
      continue;
 
233
    }
 
234
    Q_ASSERT( item.parentCollection().isValid() );
 
235
    mCollectionItems[item.parentCollection()].append( item ); //Sort by parent col ( = restore collection)
 
236
  }
 
237
 
 
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)) );
 
242
  }
 
243
 
 
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";
 
249
    q->emitResult();
 
250
  }
 
251
 
 
252
}
 
253
 
 
254
void TrashJob::TrashJobPrivate::collectionsReceived( const Akonadi::Collection::List &collections )
 
255
{
 
256
  Q_Q( TrashJob );
 
257
  if ( collections.isEmpty() ) {
 
258
    q->setError( Job::Unknown );
 
259
    q->setErrorText( i18n( "Invalid collection passed" ) );
 
260
    q->emitResult();
 
261
    return;
 
262
  }
 
263
  Q_ASSERT( collections.size() == 1 );
 
264
  mCollection = collections.first();
 
265
 
 
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*)) );
 
270
    } else {
 
271
      kWarning() << "Nothing to do";
 
272
      q->emitResult();
 
273
    }
 
274
    return;
 
275
  }
 
276
 
 
277
  Collection trashCollection = mTrashCollection;
 
278
  if ( !mTrashCollection.isValid() ) {
 
279
    trashCollection = TrashSettings::getTrashCollection( mCollection.resource() );
 
280
  }
 
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
 
286
  }
 
287
 
 
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*)) );
 
292
  } else {
 
293
    setAttribute( Collection::List() << mCollection );
 
294
  }
 
295
 
 
296
}
 
297
 
 
298
 
 
299
 
 
300
 
 
301
TrashJob::TrashJob( const Item & item, QObject * parent )
 
302
    : Job( new TrashJobPrivate( this ), parent )
 
303
{
 
304
  Q_D( TrashJob );
 
305
  d->mItems << item;
 
306
}
 
307
 
 
308
TrashJob::TrashJob( const Item::List& items, QObject* parent )
 
309
    : Job( new TrashJobPrivate( this ), parent )
 
310
{
 
311
  Q_D( TrashJob );
 
312
  d->mItems = items;
 
313
}
 
314
 
 
315
TrashJob::TrashJob( const Collection& collection, QObject* parent )
 
316
    : Job( new TrashJobPrivate( this ), parent )
 
317
{
 
318
  Q_D( TrashJob );
 
319
  d->mCollection = collection;
 
320
}
 
321
 
 
322
TrashJob::~TrashJob()
 
323
{
 
324
}
 
325
 
 
326
Item::List TrashJob::items() const
 
327
{
 
328
  Q_D( const TrashJob );
 
329
  return d->mItems;
 
330
}
 
331
 
 
332
void TrashJob::setTrashCollection( const Akonadi::Collection &collection )
 
333
{
 
334
  Q_D( TrashJob );
 
335
  d->mTrashCollection = collection;
 
336
}
 
337
 
 
338
void TrashJob::keepTrashInCollection( bool enable )
 
339
{
 
340
  Q_D( TrashJob );
 
341
  d->mKeepTrashInCollection = enable;
 
342
}
 
343
 
 
344
void TrashJob::deleteIfInTrash( bool enable )
 
345
{
 
346
  Q_D( TrashJob );
 
347
  d->mDeleteIfInTrash = enable;
 
348
}
 
349
 
 
350
void TrashJob::doStart()
 
351
{
 
352
  Q_D( TrashJob );
 
353
 
 
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)) );
 
361
 
 
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)) );
 
366
 
 
367
  } else {
 
368
    kWarning() << "No valid collection or empty itemlist";
 
369
    setError( Job::Unknown );
 
370
    setErrorText( i18n( "No valid collection or empty itemlist" ) );
 
371
    emitResult();
 
372
  }
 
373
}
 
374
 
 
375
#include "trashjob.moc"