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( const QList<Collection::Id> & cols, Type type, QObject * parent ) 00141 : Job( new CollectionFetchJobPrivate( this ), parent ) 00142 { 00143 Q_D( CollectionFetchJob ); 00144 d->init(); 00145 00146 Q_ASSERT( !cols.isEmpty() ); 00147 if ( cols.size() == 1 ) { 00148 d->mBase = Collection(cols.first()); 00149 } else { 00150 foreach(Collection::Id id, cols) 00151 d->mBaseList.append(Collection(id)); 00152 } 00153 d->mType = type; 00154 } 00155 00156 CollectionFetchJob::~CollectionFetchJob() 00157 { 00158 } 00159 00160 Akonadi::Collection::List CollectionFetchJob::collections() const 00161 { 00162 Q_D( const CollectionFetchJob ); 00163 00164 return d->mCollections; 00165 } 00166 00167 void CollectionFetchJob::doStart() 00168 { 00169 Q_D( CollectionFetchJob ); 00170 00171 if ( !d->mBaseList.isEmpty() ) { 00172 if ( d->mType == Recursive ) { 00173 // Because doStart starts several subjobs and @p cols could contain descendants of 00174 // other elements in the list, if type is Recusrive, we could end up with duplicates in the result. 00175 // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors, 00176 // Iterate over that result removing intersections and then perform the Recursive fetch on 00177 // the remainder. 00178 d->mBasePrefetch = true; 00179 // No need to connect to the collectionsReceived signal here. This job is internal. The 00180 // result needs to be filtered through filterDescendants before it is useful. 00181 new CollectionFetchJob( d->mBaseList, NonOverlappingRoots, this ); 00182 } else if ( d->mType == NonOverlappingRoots ) { 00183 foreach ( const Collection &col, d->mBaseList ) { 00184 // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated) 00185 // result needs to be filtered through filterDescendants before it is useful. 00186 CollectionFetchJob *subJob = new CollectionFetchJob( col, Base, this ); 00187 subJob->fetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::All ); 00188 } 00189 } else { 00190 foreach ( const Collection &col, d->mBaseList ) { 00191 CollectionFetchJob *subJob = new CollectionFetchJob( col, d->mType, this ); 00192 connect( subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List))); 00193 subJob->setFetchScope( fetchScope() ); 00194 } 00195 } 00196 return; 00197 } 00198 00199 if ( !d->mBase.isValid() && d->mBase.remoteId().isEmpty() ) { 00200 setError( Unknown ); 00201 setErrorText( i18n( "Invalid collection given." ) ); 00202 emitResult(); 00203 return; 00204 } 00205 00206 QByteArray command = d->newTag(); 00207 if ( !d->mBase.isValid() ) { 00208 if ( CollectionUtils::hasValidHierarchicalRID( d->mBase ) ) 00209 command += " HRID"; 00210 else 00211 command += " " AKONADI_CMD_RID; 00212 } 00213 if ( d->mScope.includeUnsubscribed() ) 00214 command += " LIST "; 00215 else 00216 command += " LSUB "; 00217 00218 if ( d->mBase.isValid() ) 00219 command += QByteArray::number( d->mBase.id() ); 00220 else if ( CollectionUtils::hasValidHierarchicalRID( d->mBase ) ) 00221 command += '(' + ProtocolHelper::hierarchicalRidToByteArray( d->mBase ) + ')'; 00222 else 00223 command += ImapParser::quote( d->mBase.remoteId().toUtf8() ); 00224 00225 command += ' '; 00226 switch ( d->mType ) { 00227 case Base: 00228 command += "0 ("; 00229 break; 00230 case FirstLevel: 00231 command += "1 ("; 00232 break; 00233 case Recursive: 00234 command += "INF ("; 00235 break; 00236 default: 00237 Q_ASSERT( false ); 00238 } 00239 00240 QList<QByteArray> filter; 00241 if ( !d->mScope.resource().isEmpty() ) { 00242 filter.append( "RESOURCE" ); 00243 // FIXME: Does this need to be quoted?? 00244 filter.append( d->mScope.resource().toUtf8() ); 00245 } 00246 00247 if ( !d->mScope.contentMimeTypes().isEmpty() ) { 00248 filter.append( "MIMETYPE" ); 00249 QList<QByteArray> mts; 00250 foreach ( const QString &mt, d->mScope.contentMimeTypes() ) 00251 // FIXME: Does this need to be quoted?? 00252 mts.append( mt.toUtf8() ); 00253 filter.append( '(' + ImapParser::join( mts, " " ) + ')' ); 00254 } 00255 00256 QList<QByteArray> options; 00257 if ( d->mScope.includeStatistics() ) { 00258 options.append( "STATISTICS" ); 00259 options.append( "true" ); 00260 } 00261 if ( d->mScope.ancestorRetrieval() != CollectionFetchScope::None ) { 00262 options.append( "ANCESTORS" ); 00263 switch ( d->mScope.ancestorRetrieval() ) { 00264 case CollectionFetchScope::None: 00265 options.append( "0" ); 00266 break; 00267 case CollectionFetchScope::Parent: 00268 options.append( "1" ); 00269 break; 00270 case CollectionFetchScope::All: 00271 options.append( "INF" ); 00272 break; 00273 default: 00274 Q_ASSERT( false ); 00275 } 00276 } 00277 00278 command += ImapParser::join( filter, " " ) + ") (" + ImapParser::join( options, " " ) + ")\n"; 00279 d->writeData( command ); 00280 } 00281 00282 void CollectionFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data ) 00283 { 00284 Q_D( CollectionFetchJob ); 00285 00286 if ( d->mBasePrefetch || d->mType == NonOverlappingRoots ) 00287 return; 00288 00289 if ( tag == "*" ) { 00290 Collection collection; 00291 ProtocolHelper::parseCollection( data, collection ); 00292 if ( !collection.isValid() ) 00293 return; 00294 00295 collection.d_ptr->resetChangeLog(); 00296 d->mCollections.append( collection ); 00297 d->mPendingCollections.append( collection ); 00298 if ( !d->mEmitTimer->isActive() ) 00299 d->mEmitTimer->start(); 00300 return; 00301 } 00302 kDebug() << "Unhandled server response" << tag << data; 00303 } 00304 00305 void CollectionFetchJob::setResource(const QString & resource) 00306 { 00307 Q_D( CollectionFetchJob ); 00308 00309 d->mScope.setResource( resource ); 00310 } 00311 00312 static Collection::List filterDescendants( const Collection::List &list ) 00313 { 00314 Collection::List result; 00315 00316 QVector<QList<Collection::Id> > ids; 00317 foreach( const Collection &collection, list ) { 00318 QList<Collection::Id> ancestors; 00319 Collection parent = collection.parentCollection(); 00320 ancestors << parent.id(); 00321 if ( parent != Collection::root() ) { 00322 while ( parent.parentCollection() != Collection::root() ) { 00323 parent = parent.parentCollection(); 00324 QList<Collection::Id>::iterator i = qLowerBound( ancestors.begin(), ancestors.end(), parent.id() ); 00325 ancestors.insert( i, parent.id() ); 00326 } 00327 } 00328 ids << ancestors; 00329 } 00330 00331 QSet<Collection::Id> excludeList; 00332 foreach ( const Collection &collection, list ) { 00333 int i = 0; 00334 foreach( const QList<Collection::Id> &ancestors, ids ) { 00335 if ( qBinaryFind( ancestors, collection.id() ) != ancestors.end() ) { 00336 excludeList.insert( list.at( i ).id() ); 00337 } 00338 ++i; 00339 } 00340 } 00341 00342 foreach ( const Collection &collection, list ) { 00343 if ( !excludeList.contains( collection.id() ) ) 00344 result.append( collection ); 00345 } 00346 00347 return result; 00348 } 00349 00350 void CollectionFetchJob::slotResult(KJob * job) 00351 { 00352 Q_D( CollectionFetchJob ); 00353 00354 CollectionFetchJob *list = qobject_cast<CollectionFetchJob*>( job ); 00355 Q_ASSERT( job ); 00356 if ( d->mBasePrefetch ) { 00357 d->mBasePrefetch = false; 00358 const Collection::List roots = list->collections(); 00359 Job::slotResult( job ); 00360 Q_ASSERT( !hasSubjobs() ); 00361 if ( !job->error() ) { 00362 foreach ( const Collection &col, roots ) { 00363 CollectionFetchJob *subJob = new CollectionFetchJob( col, d->mType, this ); 00364 connect( subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List)) ); 00365 subJob->setFetchScope( fetchScope() ); 00366 } 00367 } 00368 // No result yet. 00369 } else if ( d->mType == NonOverlappingRoots ) { 00370 d->mPrefetchList += list->collections(); 00371 Job::slotResult( job ); 00372 if ( !job->error() && !hasSubjobs() ) { 00373 const Collection::List result = filterDescendants( d->mPrefetchList ); 00374 d->mPendingCollections += result; 00375 d->mCollections = result; 00376 d->flushIterativeResult(); 00377 emitResult(); 00378 } 00379 } else { 00380 // We need to tell the subjob to emit its collectionsReceived signal before 00381 // the result signal is emitted. That will populate my mPendingCollections 00382 // which will be flushed by calling emitResult which will cause 00383 // CollectionFetchJobPrivate::timeout to be called. 00384 list->d_func()->flushIterativeResult(); 00385 d->mCollections += list->collections(); 00386 // Pending collections should have already been emitted by listening to (and flushing) the job. 00387 Job::slotResult( job ); 00388 if ( !job->error() && !hasSubjobs() ) 00389 emitResult(); 00390 } 00391 } 00392 00393 void CollectionFetchJob::includeUnsubscribed(bool include) 00394 { 00395 Q_D( CollectionFetchJob ); 00396 00397 d->mScope.setIncludeUnsubscribed( include ); 00398 } 00399 00400 void CollectionFetchJob::includeStatistics(bool include) 00401 { 00402 Q_D( CollectionFetchJob ); 00403 00404 d->mScope.setIncludeStatistics( include ); 00405 } 00406 00407 void CollectionFetchJob::setFetchScope( const CollectionFetchScope &scope ) 00408 { 00409 Q_D( CollectionFetchJob ); 00410 d->mScope = scope; 00411 } 00412 00413 CollectionFetchScope& CollectionFetchJob::fetchScope() 00414 { 00415 Q_D( CollectionFetchJob ); 00416 return d->mScope; 00417 } 00418 00419 #include "collectionfetchjob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Apr 30 2012 21:49:14 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Apr 30 2012 21:49:14 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.