akonadi
collectionfetchjob.cpp
00001 /* 00002 Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org> 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 "collectionfetchjob.h" 00021 00022 #include "imapparser_p.h" 00023 #include "job_p.h" 00024 #include "protocol_p.h" 00025 #include "protocolhelper_p.h" 00026 #include "entity_p.h" 00027 #include "collectionfetchscope.h" 00028 #include "collectionutils_p.h" 00029 00030 #include <kdebug.h> 00031 #include <KLocale> 00032 00033 #include <QtCore/QHash> 00034 #include <QtCore/QStringList> 00035 #include <QtCore/QTimer> 00036 00037 using namespace Akonadi; 00038 00039 class Akonadi::CollectionFetchJobPrivate : public JobPrivate 00040 { 00041 public: 00042 CollectionFetchJobPrivate( CollectionFetchJob *parent ) 00043 : JobPrivate( parent ), mBasePrefetch( false ) 00044 { 00045 00046 } 00047 00048 void init() 00049 { 00050 mEmitTimer = new QTimer( q_ptr ); 00051 mEmitTimer->setSingleShot( true ); 00052 mEmitTimer->setInterval( 100 ); 00053 q_ptr->connect( mEmitTimer, SIGNAL( timeout() ), q_ptr, SLOT( timeout() ) ); 00054 q_ptr->connect( q_ptr, SIGNAL( result( KJob* ) ), q_ptr, SLOT( timeout() ) ); 00055 } 00056 00057 Q_DECLARE_PUBLIC( CollectionFetchJob ) 00058 00059 CollectionFetchJob::Type mType; 00060 Collection mBase; 00061 Collection::List mBaseList; 00062 Collection::List mCollections; 00063 CollectionFetchScope mScope; 00064 Collection::List mPendingCollections; 00065 QTimer *mEmitTimer; 00066 bool mBasePrefetch; 00067 Collection::List mPrefetchList; 00068 00069 void timeout() 00070 { 00071 Q_Q( CollectionFetchJob ); 00072 00073 mEmitTimer->stop(); // in case we are called by result() 00074 if ( !mPendingCollections.isEmpty() ) { 00075 if ( !q->error() ) 00076 emit q->collectionsReceived( mPendingCollections ); 00077 mPendingCollections.clear(); 00078 } 00079 } 00080 00081 void subJobCollectionReceived( const Akonadi::Collection::List &collections ) 00082 { 00083 mPendingCollections += collections; 00084 if ( !mEmitTimer->isActive() ) 00085 mEmitTimer->start(); 00086 } 00087 00088 void flushIterativeResult() 00089 { 00090 Q_Q( CollectionFetchJob ); 00091 00092 if ( mPendingCollections.isEmpty() ) 00093 return; 00094 00095 emit q->collectionsReceived( mPendingCollections ); 00096 mPendingCollections.clear(); 00097 } 00098 }; 00099 00100 CollectionFetchJob::CollectionFetchJob( const Collection &collection, Type type, QObject *parent ) 00101 : Job( new CollectionFetchJobPrivate( this ), parent ) 00102 { 00103 Q_D( CollectionFetchJob ); 00104 d->init(); 00105 00106 d->mBase = collection; 00107 d->mType = type; 00108 } 00109 00110 CollectionFetchJob::CollectionFetchJob( const Collection::List & cols, QObject * parent ) 00111 : Job( new CollectionFetchJobPrivate( this ), parent ) 00112 { 00113 Q_D( CollectionFetchJob ); 00114 d->init(); 00115 00116 Q_ASSERT( !cols.isEmpty() ); 00117 if ( cols.size() == 1 ) { 00118 d->mBase = cols.first(); 00119 } else { 00120 d->mBaseList = cols; 00121 } 00122 d->mType = CollectionFetchJob::Base; 00123 } 00124 00125 CollectionFetchJob::CollectionFetchJob( const Collection::List & cols, Type type, QObject * parent ) 00126 : Job( new CollectionFetchJobPrivate( this ), parent ) 00127 { 00128 Q_D( CollectionFetchJob ); 00129 d->init(); 00130 00131 Q_ASSERT( !cols.isEmpty() ); 00132 if ( cols.size() == 1 ) { 00133 d->mBase = cols.first(); 00134 } else { 00135 d->mBaseList = cols; 00136 } 00137 d->mType = type; 00138 } 00139 00140 CollectionFetchJob::~CollectionFetchJob() 00141 { 00142 } 00143 00144 Akonadi::Collection::List CollectionFetchJob::collections() const 00145 { 00146 Q_D( const CollectionFetchJob ); 00147 00148 return d->mCollections; 00149 } 00150 00151 void CollectionFetchJob::doStart() 00152 { 00153 Q_D( CollectionFetchJob ); 00154 00155 if ( !d->mBaseList.isEmpty() ) { 00156 if ( d->mType == Recursive ) { 00157 // Because doStart starts several subjobs and @p cols could contain descendants of 00158 // other elements in the list, if type is Recusrive, we could end up with duplicates in the result. 00159 // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors, 00160 // Iterate over that result removing intersections and then perform the Recursive fetch on 00161 // the remainder. 00162 d->mBasePrefetch = true; 00163 // No need to connect to the collectionsReceived signal here. This job is internal. The 00164 // result needs to be filtered through filterDescendants before it is useful. 00165 new CollectionFetchJob( d->mBaseList, NonOverlappingRoots, this ); 00166 } else if ( d->mType == NonOverlappingRoots ) { 00167 foreach ( const Collection &col, d->mBaseList ) { 00168 // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated) 00169 // result needs to be filtered through filterDescendants before it is useful. 00170 CollectionFetchJob *subJob = new CollectionFetchJob( col, Base, this ); 00171 subJob->fetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::All ); 00172 } 00173 } else { 00174 foreach ( const Collection &col, d->mBaseList ) { 00175 CollectionFetchJob *subJob = new CollectionFetchJob( col, d->mType, this ); 00176 connect( subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List))); 00177 subJob->setFetchScope( fetchScope() ); 00178 } 00179 } 00180 return; 00181 } 00182 00183 if ( !d->mBase.isValid() && d->mBase.remoteId().isEmpty() ) { 00184 setError( Unknown ); 00185 setErrorText( i18n( "Invalid collection given." ) ); 00186 emitResult(); 00187 return; 00188 } 00189 00190 QByteArray command = d->newTag(); 00191 if ( !d->mBase.isValid() ) { 00192 if ( CollectionUtils::hasValidHierarchicalRID( d->mBase ) ) 00193 command += " HRID"; 00194 else 00195 command += " " AKONADI_CMD_RID; 00196 } 00197 if ( d->mScope.includeUnsubscribed() ) 00198 command += " LIST "; 00199 else 00200 command += " LSUB "; 00201 00202 if ( d->mBase.isValid() ) 00203 command += QByteArray::number( d->mBase.id() ); 00204 else if ( CollectionUtils::hasValidHierarchicalRID( d->mBase ) ) 00205 command += '(' + ProtocolHelper::hierarchicalRidToByteArray( d->mBase ) + ')'; 00206 else 00207 command += ImapParser::quote( d->mBase.remoteId().toUtf8() ); 00208 00209 command += ' '; 00210 switch ( d->mType ) { 00211 case Base: 00212 command += "0 ("; 00213 break; 00214 case FirstLevel: 00215 command += "1 ("; 00216 break; 00217 case Recursive: 00218 command += "INF ("; 00219 break; 00220 default: 00221 Q_ASSERT( false ); 00222 } 00223 00224 QList<QByteArray> filter; 00225 if ( !d->mScope.resource().isEmpty() ) { 00226 filter.append( "RESOURCE" ); 00227 // FIXME: Does this need to be quoted?? 00228 filter.append( d->mScope.resource().toUtf8() ); 00229 } 00230 00231 if ( !d->mScope.contentMimeTypes().isEmpty() ) { 00232 filter.append( "MIMETYPE" ); 00233 QList<QByteArray> mts; 00234 foreach ( const QString &mt, d->mScope.contentMimeTypes() ) 00235 // FIXME: Does this need to be quoted?? 00236 mts.append( mt.toUtf8() ); 00237 filter.append( '(' + ImapParser::join( mts, " " ) + ')' ); 00238 } 00239 00240 QList<QByteArray> options; 00241 if ( d->mScope.includeStatistics() ) { 00242 options.append( "STATISTICS" ); 00243 options.append( "true" ); 00244 } 00245 if ( d->mScope.ancestorRetrieval() != CollectionFetchScope::None ) { 00246 options.append( "ANCESTORS" ); 00247 switch ( d->mScope.ancestorRetrieval() ) { 00248 case CollectionFetchScope::None: 00249 options.append( "0" ); 00250 break; 00251 case CollectionFetchScope::Parent: 00252 options.append( "1" ); 00253 break; 00254 case CollectionFetchScope::All: 00255 options.append( "INF" ); 00256 break; 00257 default: 00258 Q_ASSERT( false ); 00259 } 00260 } 00261 00262 command += ImapParser::join( filter, " " ) + ") (" + ImapParser::join( options, " " ) + ")\n"; 00263 d->writeData( command ); 00264 } 00265 00266 void CollectionFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) 00267 { 00268 Q_D( CollectionFetchJob ); 00269 00270 if ( d->mBasePrefetch || d->mType == NonOverlappingRoots ) 00271 return; 00272 00273 if ( tag == "*" ) { 00274 Collection collection; 00275 ProtocolHelper::parseCollection( data, collection ); 00276 if ( !collection.isValid() ) 00277 return; 00278 00279 collection.d_ptr->resetChangeLog(); 00280 d->mCollections.append( collection ); 00281 d->mPendingCollections.append( collection ); 00282 if ( !d->mEmitTimer->isActive() ) 00283 d->mEmitTimer->start(); 00284 return; 00285 } 00286 kDebug() << "Unhandled server response" << tag << data; 00287 } 00288 00289 void CollectionFetchJob::setResource(const QString & resource) 00290 { 00291 Q_D( CollectionFetchJob ); 00292 00293 d->mScope.setResource( resource ); 00294 } 00295 00296 static Collection::List filterDescendants( const Collection::List &list ) 00297 { 00298 Collection::List result; 00299 00300 QVector<QList<Collection::Id> > ids; 00301 foreach( const Collection &collection, list ) { 00302 QList<Collection::Id> ancestors; 00303 Collection parent = collection.parentCollection(); 00304 ancestors << parent.id(); 00305 if ( parent != Collection::root() ) { 00306 while ( parent.parentCollection() != Collection::root() ) { 00307 parent = parent.parentCollection(); 00308 QList<Collection::Id>::iterator i = qLowerBound( ancestors.begin(), ancestors.end(), parent.id() ); 00309 ancestors.insert( i, parent.id() ); 00310 } 00311 } 00312 ids << ancestors; 00313 } 00314 00315 QSet<Collection::Id> excludeList; 00316 foreach ( const Collection &collection, list ) { 00317 int i = 0; 00318 foreach( const QList<Collection::Id> &ancestors, ids ) { 00319 if ( qBinaryFind( ancestors, collection.id() ) != ancestors.end() ) { 00320 excludeList.insert( list.at( i ).id() ); 00321 } 00322 ++i; 00323 } 00324 } 00325 00326 foreach ( const Collection &collection, list ) { 00327 if ( !excludeList.contains( collection.id() ) ) 00328 result.append( collection ); 00329 } 00330 00331 return result; 00332 } 00333 00334 void CollectionFetchJob::slotResult(KJob * job) 00335 { 00336 Q_D( CollectionFetchJob ); 00337 00338 CollectionFetchJob *list = qobject_cast<CollectionFetchJob*>( job ); 00339 Q_ASSERT( job ); 00340 if ( d->mBasePrefetch ) { 00341 d->mBasePrefetch = false; 00342 const Collection::List roots = list->collections(); 00343 Job::slotResult( job ); 00344 Q_ASSERT( !hasSubjobs() ); 00345 if ( !job->error() ) { 00346 foreach ( const Collection &col, roots ) { 00347 CollectionFetchJob *subJob = new CollectionFetchJob( col, d->mType, this ); 00348 connect( subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List)) ); 00349 subJob->setFetchScope( fetchScope() ); 00350 } 00351 } 00352 // No result yet. 00353 } else if ( d->mType == NonOverlappingRoots ) { 00354 d->mPrefetchList += list->collections(); 00355 Job::slotResult( job ); 00356 if ( !job->error() && !hasSubjobs() ) { 00357 const Collection::List result = filterDescendants( d->mPrefetchList ); 00358 d->mPendingCollections += result; 00359 d->mCollections = result; 00360 d->flushIterativeResult(); 00361 emitResult(); 00362 } 00363 } else { 00364 // We need to tell the subjob to emit its collectionsReceived signal before 00365 // the result signal is emitted. That will populate my mPendingCollections 00366 // which will be flushed by calling emitResult which will cause 00367 // CollectionFetchJobPrivate::timeout to be called. 00368 list->d_func()->flushIterativeResult(); 00369 d->mCollections += list->collections(); 00370 // Pending collections should have already been emitted by listening to (and flushing) the job. 00371 Job::slotResult( job ); 00372 if ( !job->error() && !hasSubjobs() ) 00373 emitResult(); 00374 } 00375 } 00376 00377 void CollectionFetchJob::includeUnsubscribed(bool include) 00378 { 00379 Q_D( CollectionFetchJob ); 00380 00381 d->mScope.setIncludeUnsubscribed( include ); 00382 } 00383 00384 void CollectionFetchJob::includeStatistics(bool include) 00385 { 00386 Q_D( CollectionFetchJob ); 00387 00388 d->mScope.setIncludeStatistics( include ); 00389 } 00390 00391 void CollectionFetchJob::setFetchScope( const CollectionFetchScope &scope ) 00392 { 00393 Q_D( CollectionFetchJob ); 00394 d->mScope = scope; 00395 } 00396 00397 CollectionFetchScope& CollectionFetchJob::fetchScope() 00398 { 00399 Q_D( CollectionFetchJob ); 00400 return d->mScope; 00401 } 00402 00403 #include "collectionfetchjob.moc"