akonadi
trashjob.cpp
00001 /* 00002 Copyright (c) 2011 Christian Mollekopf <chrigi_1@fastmail.fm> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 00018 */ 00019 00020 #include "trashjob.h" 00021 00022 #include "collection.h" 00023 #include "entitydeletedattribute.h" 00024 #include "item.h" 00025 #include "job_p.h" 00026 #include "trashsettings.h" 00027 00028 #include <KLocale> 00029 00030 #include <akonadi/itemdeletejob.h> 00031 #include <akonadi/collectiondeletejob.h> 00032 #include <akonadi/itemmovejob.h> 00033 #include <akonadi/collectionmovejob.h> 00034 #include <akonadi/itemmodifyjob.h> 00035 #include <akonadi/collectionmodifyjob.h> 00036 #include <akonadi/itemfetchscope.h> 00037 #include <akonadi/collectionfetchscope.h> 00038 #include <akonadi/itemfetchjob.h> 00039 #include <akonadi/collectionfetchjob.h> 00040 00041 #include <QHash> 00042 00043 using namespace Akonadi; 00044 00045 class TrashJob::TrashJobPrivate : public JobPrivate 00046 { 00047 public: 00048 TrashJobPrivate( TrashJob *parent ) 00049 : JobPrivate( parent ), 00050 mKeepTrashInCollection( false ), 00051 mSetRestoreCollection( false ), 00052 mDeleteIfInTrash( false ) { 00053 } 00054 //4. 00055 void selectResult( KJob *job ); 00056 //3. 00057 //Helper functions to recursivly set the attribute on deleted collections 00058 void setAttribute( const Akonadi::Collection::List & ); 00059 void setAttribute( const Akonadi::Item::List & ); 00060 //Set attributes after ensuring that move job was successful 00061 void setAttribute( KJob *job ); 00062 00063 //2. 00064 //called after parent of the trashed item was fetched (needed to see in which resource the item is in) 00065 void parentCollectionReceived( const Akonadi::Collection::List & ); 00066 00067 00068 //1. 00069 //called after initial fetch of trashed items 00070 void itemsReceived( const Akonadi::Item::List & ); 00071 //called after initial fetch of trashed collection 00072 void collectionsReceived( const Akonadi::Collection::List & ); 00073 00074 00075 Q_DECLARE_PUBLIC( TrashJob ) 00076 00077 Item::List mItems; 00078 Collection mCollection; 00079 Collection mRestoreCollection; 00080 Collection mTrashCollection; 00081 bool mKeepTrashInCollection; 00082 bool mSetRestoreCollection; //only set restore collection when moved to trash collection (not in place) 00083 bool mDeleteIfInTrash; 00084 QHash<Collection, Item::List> mCollectionItems; //list of trashed items sorted according to parent collection 00085 QHash<Entity::Id, Collection> mParentCollections; //fetched parent collcetion of items (containing the resource name) 00086 00087 }; 00088 00089 void TrashJob::TrashJobPrivate::selectResult( KJob *job ) 00090 { 00091 Q_Q( TrashJob ); 00092 if ( job->error() ) { 00093 kWarning() << job->objectName(); 00094 kWarning() << job->errorString(); 00095 return; // KCompositeJob takes care of errors 00096 } 00097 00098 if ( !q->hasSubjobs() || ( q->subjobs().contains( static_cast<KJob*>( q->sender() ) ) && q->subjobs().size() == 1 ) ) { 00099 q->emitResult(); 00100 } 00101 } 00102 00103 void TrashJob::TrashJobPrivate::setAttribute( const Akonadi::Collection::List &list ) 00104 { 00105 Q_Q( TrashJob ); 00106 QListIterator<Collection> i( list ); 00107 while ( i.hasNext() ) { 00108 const Collection &col = i.next(); 00109 EntityDeletedAttribute *eda = new EntityDeletedAttribute(); 00110 if ( mSetRestoreCollection ) { 00111 Q_ASSERT( mRestoreCollection.isValid() ); 00112 eda->setRestoreCollection( mRestoreCollection ); 00113 } 00114 00115 Collection modCol( col.id() ); //really only modify attribute (forget old remote ids, etc.), otherwise we have an error because of the move 00116 modCol.addAttribute( eda ); 00117 00118 CollectionModifyJob *job = new CollectionModifyJob( modCol, q ); 00119 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00120 00121 ItemFetchJob *itemFetchJob = new ItemFetchJob( col, q ); 00122 //TODO not sure if it is guaranteed that itemsReceived is always before result (otherwise the result is emitted before the attributes are set) 00123 q->connect( itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(setAttribute(Akonadi::Item::List)) ); 00124 q->connect( itemFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00125 } 00126 } 00127 00128 void TrashJob::TrashJobPrivate::setAttribute( const Akonadi::Item::List &list ) 00129 { 00130 Q_Q( TrashJob ); 00131 Item::List items = list; 00132 QMutableListIterator<Item> i( items ); 00133 while ( i.hasNext() ) { 00134 const Item &item = i.next(); 00135 EntityDeletedAttribute *eda = new EntityDeletedAttribute(); 00136 if ( mSetRestoreCollection ) { 00137 //When deleting a collection, we want to restore the deleted collection's items restored to the deleted collection's parent, not the items parent 00138 if ( mRestoreCollection.isValid() ) { 00139 eda->setRestoreCollection( mRestoreCollection ); 00140 } else { 00141 Q_ASSERT( mParentCollections.contains( item.parentCollection().id() ) ); 00142 eda->setRestoreCollection( mParentCollections.value( item.parentCollection().id() ) ); 00143 } 00144 } 00145 00146 Item modItem( item.id() ); //really only modify attribute (forget old remote ids, etc.) 00147 modItem.addAttribute( eda ); 00148 ItemModifyJob *job = new ItemModifyJob( modItem, q ); 00149 job->setIgnorePayload( true ); 00150 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00151 } 00152 00153 //For some reason it is not possible to apply this change to multiple items at once 00154 /*ItemModifyJob *job = new ItemModifyJob(items, q); 00155 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) );*/ 00156 } 00157 00158 void TrashJob::TrashJobPrivate::setAttribute( KJob* job ) 00159 { 00160 Q_Q( TrashJob ); 00161 if ( job->error() ) { 00162 kWarning() << job->objectName(); 00163 kWarning() << job->errorString(); 00164 q->setError( Job::Unknown ); 00165 q->setErrorText( i18n( "Move to trash collection failed, aborting trash operation" ) ); 00166 return; 00167 } 00168 00169 //For Items 00170 const QVariant var = job->property( "MovedItems" ); 00171 if ( var.isValid() ) { 00172 int id = var.toInt(); 00173 Q_ASSERT( id >= 0 ); 00174 setAttribute( mCollectionItems.value( Collection( id ) ) ); 00175 return; 00176 } 00177 00178 //For a collection 00179 Q_ASSERT( mCollection.isValid() ); 00180 setAttribute( Collection::List() << mCollection ); 00181 //Set the attribute on all subcollections and items 00182 CollectionFetchJob *colFetchJob = new CollectionFetchJob( mCollection, CollectionFetchJob::Recursive, q ); 00183 q->connect( colFetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(setAttribute(Akonadi::Collection::List)) ); 00184 q->connect( colFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00185 } 00186 00187 void TrashJob::TrashJobPrivate::parentCollectionReceived( const Akonadi::Collection::List &collections ) 00188 { 00189 Q_Q( TrashJob ); 00190 Q_ASSERT( collections.size() == 1 ); 00191 const Collection &parentCollection = collections.first(); 00192 00193 //store attribute 00194 Q_ASSERT( !parentCollection.resource().isEmpty() ); 00195 Collection trashCollection = mTrashCollection; 00196 if ( !mTrashCollection.isValid() ) { 00197 trashCollection = TrashSettings::getTrashCollection( parentCollection.resource() ); 00198 } 00199 if ( !mKeepTrashInCollection && trashCollection.isValid() ) { //Only set the restore collection if the item is moved to trash 00200 mSetRestoreCollection = true; 00201 } 00202 00203 mParentCollections.insert( parentCollection.id(), parentCollection ); 00204 00205 if ( trashCollection.isValid() ) { //Move the items to the correct collection if available 00206 ItemMoveJob *job = new ItemMoveJob( mCollectionItems.value( parentCollection ), trashCollection, q ); 00207 job->setProperty( "MovedItems", parentCollection.id() ); 00208 q->connect( job, SIGNAL(result(KJob*)), SLOT(setAttribute(KJob*)) ); //Wait until the move finished to set the attirbute 00209 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00210 } else { 00211 setAttribute( mCollectionItems.value( parentCollection ) ); 00212 } 00213 } 00214 00215 void TrashJob::TrashJobPrivate::itemsReceived( const Akonadi::Item::List &items ) 00216 { 00217 Q_Q( TrashJob ); 00218 if ( items.isEmpty() ) { 00219 q->setError( Job::Unknown ); 00220 q->setErrorText( i18n( "Invalid items passed" ) ); 00221 q->emitResult(); 00222 return; 00223 } 00224 00225 Item::List toDelete; 00226 00227 QListIterator<Item> i( items ); 00228 while ( i.hasNext() ) { 00229 const Item &item = i.next(); 00230 if ( item.hasAttribute<EntityDeletedAttribute>() ) { 00231 toDelete.append( item ); 00232 continue; 00233 } 00234 Q_ASSERT( item.parentCollection().isValid() ); 00235 mCollectionItems[item.parentCollection()].append( item ); //Sort by parent col ( = restore collection) 00236 } 00237 00238 foreach( const Collection &col, mCollectionItems.keys() ) { //krazy:exclude=foreach 00239 CollectionFetchJob *job = new CollectionFetchJob( col, Akonadi::CollectionFetchJob::Base, q ); 00240 q->connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), 00241 SLOT(parentCollectionReceived(Akonadi::Collection::List)) ); 00242 } 00243 00244 if ( mDeleteIfInTrash && !toDelete.isEmpty() ) { 00245 ItemDeleteJob *job = new ItemDeleteJob( toDelete, q ); 00246 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00247 } else if ( mCollectionItems.isEmpty() ) { //No job started, so we abort the job 00248 kWarning() << "Nothing to do"; 00249 q->emitResult(); 00250 } 00251 00252 } 00253 00254 void TrashJob::TrashJobPrivate::collectionsReceived( const Akonadi::Collection::List &collections ) 00255 { 00256 Q_Q( TrashJob ); 00257 if ( collections.isEmpty() ) { 00258 q->setError( Job::Unknown ); 00259 q->setErrorText( i18n( "Invalid collection passed" ) ); 00260 q->emitResult(); 00261 return; 00262 } 00263 Q_ASSERT( collections.size() == 1 ); 00264 mCollection = collections.first(); 00265 00266 if ( mCollection.hasAttribute<EntityDeletedAttribute>() ) {//marked as deleted 00267 if ( mDeleteIfInTrash ) { 00268 CollectionDeleteJob *job = new CollectionDeleteJob( mCollection, q ); 00269 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00270 } else { 00271 kWarning() << "Nothing to do"; 00272 q->emitResult(); 00273 } 00274 return; 00275 } 00276 00277 Collection trashCollection = mTrashCollection; 00278 if ( !mTrashCollection.isValid() ) { 00279 trashCollection = TrashSettings::getTrashCollection( mCollection.resource() ); 00280 } 00281 if ( !mKeepTrashInCollection && trashCollection.isValid() ) { //only set the restore collection if the item is moved to trash 00282 mSetRestoreCollection = true; 00283 Q_ASSERT( mCollection.parentCollection().isValid() ); 00284 mRestoreCollection = mCollection.parentCollection(); 00285 mRestoreCollection.setResource( mCollection.resource() ); //The parent collection doesn't contain the resource, so we have to set it manually 00286 } 00287 00288 if ( trashCollection.isValid() ) { 00289 CollectionMoveJob *job = new CollectionMoveJob( mCollection, trashCollection, q ); 00290 q->connect( job, SIGNAL(result(KJob*)), SLOT(setAttribute(KJob*)) ); 00291 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00292 } else { 00293 setAttribute( Collection::List() << mCollection ); 00294 } 00295 00296 } 00297 00298 00299 00300 00301 TrashJob::TrashJob( const Item & item, QObject * parent ) 00302 : Job( new TrashJobPrivate( this ), parent ) 00303 { 00304 Q_D( TrashJob ); 00305 d->mItems << item; 00306 } 00307 00308 TrashJob::TrashJob( const Item::List& items, QObject* parent ) 00309 : Job( new TrashJobPrivate( this ), parent ) 00310 { 00311 Q_D( TrashJob ); 00312 d->mItems = items; 00313 } 00314 00315 TrashJob::TrashJob( const Collection& collection, QObject* parent ) 00316 : Job( new TrashJobPrivate( this ), parent ) 00317 { 00318 Q_D( TrashJob ); 00319 d->mCollection = collection; 00320 } 00321 00322 TrashJob::~TrashJob() 00323 { 00324 } 00325 00326 Item::List TrashJob::items() const 00327 { 00328 Q_D( const TrashJob ); 00329 return d->mItems; 00330 } 00331 00332 void TrashJob::setTrashCollection( const Akonadi::Collection &collection ) 00333 { 00334 Q_D( TrashJob ); 00335 d->mTrashCollection = collection; 00336 } 00337 00338 void TrashJob::keepTrashInCollection( bool enable ) 00339 { 00340 Q_D( TrashJob ); 00341 d->mKeepTrashInCollection = enable; 00342 } 00343 00344 void TrashJob::deleteIfInTrash( bool enable ) 00345 { 00346 Q_D( TrashJob ); 00347 d->mDeleteIfInTrash = enable; 00348 } 00349 00350 void TrashJob::doStart() 00351 { 00352 Q_D( TrashJob ); 00353 00354 //Fetch items first to ensure that the EntityDeletedAttribute is available 00355 if ( !d->mItems.isEmpty() ) { 00356 ItemFetchJob *job = new ItemFetchJob( d->mItems, this ); 00357 job->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); //so we have access to the resource 00358 //job->fetchScope().setCacheOnly(true); 00359 job->fetchScope().fetchAttribute<EntityDeletedAttribute>( true ); 00360 connect( job, SIGNAL(itemsReceived(Akonadi::Item::List)), this, SLOT(itemsReceived(Akonadi::Item::List)) ); 00361 00362 } else if ( d->mCollection.isValid() ) { 00363 CollectionFetchJob *job = new CollectionFetchJob( d->mCollection, CollectionFetchJob::Base, this ); 00364 job->fetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::Parent ); 00365 connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(collectionsReceived(Akonadi::Collection::List)) ); 00366 00367 } else { 00368 kWarning() << "No valid collection or empty itemlist"; 00369 setError( Job::Unknown ); 00370 setErrorText( i18n( "No valid collection or empty itemlist" ) ); 00371 emitResult(); 00372 } 00373 } 00374 00375 #include "trashjob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:25:20 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:25:20 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.