akonadi
trashrestorejob.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 "trashrestorejob.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/collectionfetchjob.h> 00037 #include <akonadi/itemfetchjob.h> 00038 #include <akonadi/collectionfetchscope.h> 00039 #include <akonadi/itemfetchscope.h> 00040 00041 #include <QHash> 00042 00043 00044 using namespace Akonadi; 00045 00046 class TrashRestoreJob::TrashRestoreJobPrivate : public JobPrivate 00047 { 00048 public: 00049 TrashRestoreJobPrivate( TrashRestoreJob *parent ) 00050 : JobPrivate( parent ) { 00051 } 00052 00053 void selectResult( KJob * ); 00054 00055 //Called when the target collection was fetched, 00056 //will issue the move and the removal of the attributes if collection is valid 00057 void targetCollectionFetched( KJob * ); 00058 00059 void removeAttribute( const Akonadi::Item::List & ); 00060 void removeAttribute( const Akonadi::Collection::List & ); 00061 00062 //Called after initial fetch of items, issues fetch of target collection or removes attributes for in place restore 00063 void itemsReceived( const Akonadi::Item::List & ); 00064 void collectionsReceived( const Akonadi::Collection::List & ); 00065 00066 Q_DECLARE_PUBLIC( TrashRestoreJob ) 00067 00068 Item::List mItems; 00069 Collection mCollection; 00070 Collection mTargetCollection; 00071 QHash<Collection, Item::List> restoreCollections; //groups items to target restore collections 00072 00073 }; 00074 00075 void TrashRestoreJob::TrashRestoreJobPrivate::selectResult( KJob *job ) 00076 { 00077 Q_Q( TrashRestoreJob ); 00078 if ( job->error() ) { 00079 kWarning() << job->errorString(); 00080 return; // KCompositeJob takes care of errors 00081 } 00082 00083 if ( !q->hasSubjobs() || ( q->subjobs().contains( static_cast<KJob*>( q->sender() ) ) && q->subjobs().size() == 1 ) ) { 00084 //kWarning() << "trash restore finished"; 00085 q->emitResult(); 00086 } 00087 } 00088 00089 void TrashRestoreJob::TrashRestoreJobPrivate::targetCollectionFetched( KJob *job ) 00090 { 00091 Q_Q( TrashRestoreJob ); 00092 00093 CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob*> ( job ); 00094 Q_ASSERT( fetchJob ); 00095 const Collection::List &list = fetchJob->collections(); 00096 00097 if ( list.isEmpty() || !list.first().isValid() || list.first().hasAttribute<Akonadi::EntityDeletedAttribute>() ) { //target collection is invalid/not existing 00098 00099 const QString res = fetchJob->property( "Resource" ).toString(); 00100 if ( res.isEmpty() ) { //There is no fallback 00101 q->setError( Job::Unknown ); 00102 q->setErrorText( i18n( "Could not find restore collection and restore resource is not available" ) ); 00103 q->emitResult(); 00104 //FAIL 00105 kWarning() << "restore collection not available"; 00106 return; 00107 } 00108 00109 //Try again with the root collection of the resource as fallback 00110 CollectionFetchJob *resRootFetch = new CollectionFetchJob( Collection::root(), CollectionFetchJob::FirstLevel, q ); 00111 resRootFetch->fetchScope().setResource( res ); 00112 const QVariant &var = fetchJob->property( "Items" ); 00113 if ( var.isValid() ) { 00114 resRootFetch->setProperty( "Items", var.toInt() ); 00115 } 00116 q->connect( resRootFetch, SIGNAL(result(KJob*)), SLOT(targetCollectionFetched(KJob*)) ); 00117 q->connect( resRootFetch, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00118 return; 00119 } 00120 Q_ASSERT( list.size() == 1 ); 00121 //SUCCESS 00122 //We know where to move the entity, so remove the attributes and move them to the right location 00123 if ( !mItems.isEmpty() ) { 00124 const QVariant &var = fetchJob->property( "Items" ); 00125 Q_ASSERT( var.isValid() ); 00126 const Item::List &items = restoreCollections[Collection( var.toInt() )]; 00127 00128 //store removed attribute if destination collection is valid or the item doesn't have a restore collection 00129 //TODO only remove the attribute if the move job was successful (although it is unlikely that it fails since we already fetched the collection) 00130 removeAttribute( items ); 00131 if ( items.first().parentCollection() != list.first() ) { 00132 ItemMoveJob *job = new ItemMoveJob( items, list.first(), q ); 00133 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00134 } 00135 } else { 00136 Q_ASSERT( mCollection.isValid() ); 00137 //TODO only remove the attribute if the move job was successful 00138 removeAttribute( Collection::List() << mCollection ); 00139 CollectionFetchJob *collectionFetchJob = new CollectionFetchJob( mCollection, CollectionFetchJob::Recursive, q ); 00140 q->connect( collectionFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00141 q->connect( collectionFetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(removeAttribute(Akonadi::Collection::List)) ); 00142 00143 if ( mCollection.parentCollection() != list.first() ) { 00144 CollectionMoveJob *job = new CollectionMoveJob( mCollection, list.first(), q ); 00145 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00146 } 00147 } 00148 00149 } 00150 00151 void TrashRestoreJob::TrashRestoreJobPrivate::itemsReceived( const Akonadi::Item::List &items ) 00152 { 00153 Q_Q( TrashRestoreJob ); 00154 if ( items.isEmpty() ) { 00155 q->setError( Job::Unknown ); 00156 q->setErrorText( i18n( "Invalid items passed" ) ); 00157 q->emitResult(); 00158 return; 00159 } 00160 mItems = items; 00161 00162 //Sort by restore collection 00163 foreach( const Item &item, mItems ) { 00164 if ( !item.hasAttribute<Akonadi::EntityDeletedAttribute>() ) { 00165 continue; 00166 } 00167 //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 00168 restoreCollections[item.attribute<Akonadi::EntityDeletedAttribute>()->restoreCollection()].append( item ); 00169 } 00170 00171 foreach( const Collection &col, restoreCollections.keys() ) { //krazy:exclude=foreach 00172 const Item &first = restoreCollections.value( col ).first(); 00173 //Move the items to the correct collection if available 00174 Collection targetCollection = col; 00175 const QString restoreResource = first.attribute<Akonadi::EntityDeletedAttribute>()->restoreResource(); 00176 00177 //Restore in place if no restore collection is set 00178 if ( !targetCollection.isValid() ) { 00179 removeAttribute( restoreCollections.value( col ) ); 00180 return; 00181 } 00182 00183 //Explicit target overrides the resource 00184 if ( mTargetCollection.isValid() ) { 00185 targetCollection = mTargetCollection; 00186 } 00187 00188 //Try to fetch the target resource to see if it is available 00189 CollectionFetchJob *fetchJob = new CollectionFetchJob( targetCollection, Akonadi::CollectionFetchJob::Base, q ); 00190 if ( !mTargetCollection.isValid() ) { //explicit targets don't have a fallback 00191 fetchJob->setProperty( "Resource", restoreResource ); 00192 } 00193 fetchJob->setProperty( "Items", col.id() ); //to find the items in restore collections again 00194 q->connect( fetchJob, SIGNAL(result(KJob*)), SLOT(targetCollectionFetched(KJob*)) ); 00195 } 00196 } 00197 00198 00199 void TrashRestoreJob::TrashRestoreJobPrivate::collectionsReceived( const Akonadi::Collection::List &collections ) 00200 { 00201 Q_Q( TrashRestoreJob ); 00202 if ( collections.isEmpty() ) { 00203 q->setError( Job::Unknown ); 00204 q->setErrorText( i18n( "Invalid collection passed" ) ); 00205 q->emitResult(); 00206 return; 00207 } 00208 Q_ASSERT( collections.size() == 1 ); 00209 mCollection = collections.first(); 00210 00211 if ( !mCollection.hasAttribute<Akonadi::EntityDeletedAttribute>() ) { 00212 return; 00213 } 00214 00215 const QString restoreResource = mCollection.attribute<Akonadi::EntityDeletedAttribute>()->restoreResource(); 00216 Collection targetCollection = mCollection.attribute<EntityDeletedAttribute>()->restoreCollection(); 00217 00218 //Restore in place if no restore collection/resource is set 00219 if ( !targetCollection.isValid() ) { 00220 removeAttribute( Collection::List() << mCollection ); 00221 CollectionFetchJob *collectionFetchJob = new CollectionFetchJob( mCollection, CollectionFetchJob::Recursive, q ); 00222 q->connect( collectionFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00223 q->connect( collectionFetchJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(removeAttribute(Akonadi::Collection::List)) ); 00224 return; 00225 } 00226 00227 //Explicit target overrides the resource/configured restore collection 00228 if ( mTargetCollection.isValid() ) { 00229 targetCollection = mTargetCollection; 00230 } 00231 00232 //Fetch the target collection to check if it's valid 00233 CollectionFetchJob *fetchJob = new CollectionFetchJob( targetCollection, CollectionFetchJob::Base, q ); 00234 if ( !mTargetCollection.isValid() ) { //explicit targets don't have a fallback 00235 fetchJob->setProperty( "Resource", restoreResource ); 00236 } 00237 q->connect( fetchJob, SIGNAL(result(KJob*)), SLOT(targetCollectionFetched(KJob*)) ); 00238 } 00239 00240 00241 void TrashRestoreJob::TrashRestoreJobPrivate::removeAttribute( const Akonadi::Collection::List &list ) 00242 { 00243 Q_Q( TrashRestoreJob ); 00244 QListIterator<Collection> i( list ); 00245 while ( i.hasNext() ) { 00246 Collection col = i.next(); 00247 col.removeAttribute<EntityDeletedAttribute>(); 00248 00249 CollectionModifyJob *job = new CollectionModifyJob( col, q ); 00250 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00251 00252 ItemFetchJob *itemFetchJob = new ItemFetchJob( col, q ); 00253 itemFetchJob->fetchScope().fetchAttribute<EntityDeletedAttribute> ( true ); 00254 q->connect( itemFetchJob, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00255 q->connect( itemFetchJob, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(removeAttribute(Akonadi::Item::List)) ); 00256 } 00257 } 00258 00259 void TrashRestoreJob::TrashRestoreJobPrivate::removeAttribute( const Akonadi::Item::List &list ) 00260 { 00261 Q_Q( TrashRestoreJob ); 00262 Item::List items = list; 00263 QMutableListIterator<Item> i( items ); 00264 while ( i.hasNext() ) { 00265 Item &item = i.next(); 00266 item.removeAttribute<EntityDeletedAttribute>(); 00267 ItemModifyJob *job = new ItemModifyJob( item, q ); 00268 job->setIgnorePayload( true ); 00269 q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00270 } 00271 //For some reason it is not possible to apply this change to multiple items at once 00272 //ItemModifyJob *job = new ItemModifyJob(items, q); 00273 //q->connect( job, SIGNAL(result(KJob*)), SLOT(selectResult(KJob*)) ); 00274 } 00275 00276 00277 TrashRestoreJob::TrashRestoreJob( const Item & item, QObject * parent ) 00278 : Job( new TrashRestoreJobPrivate( this ), parent ) 00279 { 00280 Q_D( TrashRestoreJob ); 00281 d->mItems << item; 00282 } 00283 00284 TrashRestoreJob::TrashRestoreJob( const Item::List& items, QObject* parent ) 00285 : Job( new TrashRestoreJobPrivate( this ), parent ) 00286 { 00287 Q_D( TrashRestoreJob ); 00288 d->mItems = items; 00289 } 00290 00291 TrashRestoreJob::TrashRestoreJob( const Collection& collection, QObject* parent ) 00292 : Job( new TrashRestoreJobPrivate( this ), parent ) 00293 { 00294 Q_D( TrashRestoreJob ); 00295 d->mCollection = collection; 00296 } 00297 00298 TrashRestoreJob::~TrashRestoreJob() 00299 { 00300 } 00301 00302 void TrashRestoreJob::setTargetCollection( const Akonadi::Collection collection ) 00303 { 00304 Q_D( TrashRestoreJob ); 00305 d->mTargetCollection = collection; 00306 } 00307 00308 00309 Item::List TrashRestoreJob::items() const 00310 { 00311 Q_D( const TrashRestoreJob ); 00312 return d->mItems; 00313 } 00314 00315 void TrashRestoreJob::doStart() 00316 { 00317 Q_D( TrashRestoreJob ); 00318 00319 //We always have to fetch the entities to ensure that the EntityDeletedAttribute is available 00320 if ( !d->mItems.isEmpty() ) { 00321 ItemFetchJob *job = new ItemFetchJob( d->mItems, this ); 00322 job->fetchScope().setCacheOnly( true ); 00323 job->fetchScope().fetchAttribute<EntityDeletedAttribute> ( true ); 00324 connect( job, SIGNAL(itemsReceived(Akonadi::Item::List)), this, SLOT(itemsReceived(Akonadi::Item::List)) ); 00325 } else if ( d->mCollection.isValid() ) { 00326 CollectionFetchJob *job = new CollectionFetchJob( d->mCollection, CollectionFetchJob::Base, this ); 00327 connect( job, SIGNAL(collectionsReceived(Akonadi::Collection::List)), this, SLOT(collectionsReceived(Akonadi::Collection::List)) ); 00328 } else { 00329 kWarning() << "No valid collection or empty itemlist"; 00330 setError( Job::Unknown ); 00331 setErrorText( i18n( "No valid collection or empty itemlist" ) ); 00332 emitResult(); 00333 } 00334 00335 } 00336 00337 #include "trashrestorejob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Aug 27 2012 22:09:25 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Aug 27 2012 22:09:25 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.