• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.1 API Reference
  • KDE Home
  • Contact Us
 

akonadi

  • akonadi
collectionfetchjob.cpp
1 /*
2  Copyright (c) 2006 - 2007 Volker Krause <vkrause@kde.org>
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 "collectionfetchjob.h"
21 
22 #include "imapparser_p.h"
23 #include "job_p.h"
24 #include "protocol_p.h"
25 #include "protocolhelper_p.h"
26 #include "entity_p.h"
27 #include "collectionfetchscope.h"
28 #include "collectionutils_p.h"
29 
30 #include <kdebug.h>
31 #include <KLocale>
32 
33 #include <QtCore/QHash>
34 #include <QtCore/QStringList>
35 #include <QtCore/QTimer>
36 
37 using namespace Akonadi;
38 
39 class Akonadi::CollectionFetchJobPrivate : public JobPrivate
40 {
41  public:
42  CollectionFetchJobPrivate( CollectionFetchJob *parent )
43  : JobPrivate( parent ), mBasePrefetch( false )
44  {
45 
46  }
47 
48  void init()
49  {
50  mEmitTimer = new QTimer( q_ptr );
51  mEmitTimer->setSingleShot( true );
52  mEmitTimer->setInterval( 100 );
53  q_ptr->connect( mEmitTimer, SIGNAL(timeout()), q_ptr, SLOT(timeout()) );
54  q_ptr->connect( q_ptr, SIGNAL(result(KJob*)), q_ptr, SLOT(timeout()) );
55  }
56 
57  Q_DECLARE_PUBLIC( CollectionFetchJob )
58 
59  CollectionFetchJob::Type mType;
60  Collection mBase;
61  Collection::List mBaseList;
62  Collection::List mCollections;
63  CollectionFetchScope mScope;
64  Collection::List mPendingCollections;
65  QTimer *mEmitTimer;
66  bool mBasePrefetch;
67  Collection::List mPrefetchList;
68 
69  void timeout()
70  {
71  Q_Q( CollectionFetchJob );
72 
73  mEmitTimer->stop(); // in case we are called by result()
74  if ( !mPendingCollections.isEmpty() ) {
75  if ( !q->error() )
76  emit q->collectionsReceived( mPendingCollections );
77  mPendingCollections.clear();
78  }
79  }
80 
81  void subJobCollectionReceived( const Akonadi::Collection::List &collections )
82  {
83  mPendingCollections += collections;
84  if ( !mEmitTimer->isActive() )
85  mEmitTimer->start();
86  }
87 
88  void flushIterativeResult()
89  {
90  Q_Q( CollectionFetchJob );
91 
92  if ( mPendingCollections.isEmpty() )
93  return;
94 
95  emit q->collectionsReceived( mPendingCollections );
96  mPendingCollections.clear();
97  }
98 };
99 
100 CollectionFetchJob::CollectionFetchJob( const Collection &collection, Type type, QObject *parent )
101  : Job( new CollectionFetchJobPrivate( this ), parent )
102 {
103  Q_D( CollectionFetchJob );
104  d->init();
105 
106  d->mBase = collection;
107  d->mType = type;
108 }
109 
110 CollectionFetchJob::CollectionFetchJob( const Collection::List & cols, QObject * parent )
111  : Job( new CollectionFetchJobPrivate( this ), parent )
112 {
113  Q_D( CollectionFetchJob );
114  d->init();
115 
116  Q_ASSERT( !cols.isEmpty() );
117  if ( cols.size() == 1 ) {
118  d->mBase = cols.first();
119  } else {
120  d->mBaseList = cols;
121  }
122  d->mType = CollectionFetchJob::Base;
123 }
124 
125 CollectionFetchJob::CollectionFetchJob( const Collection::List & cols, Type type, QObject * parent )
126  : Job( new CollectionFetchJobPrivate( this ), parent )
127 {
128  Q_D( CollectionFetchJob );
129  d->init();
130 
131  Q_ASSERT( !cols.isEmpty() );
132  if ( cols.size() == 1 ) {
133  d->mBase = cols.first();
134  } else {
135  d->mBaseList = cols;
136  }
137  d->mType = type;
138 }
139 
140 CollectionFetchJob::CollectionFetchJob( const QList<Collection::Id> & cols, Type type, QObject * parent )
141  : Job( new CollectionFetchJobPrivate( this ), parent )
142 {
143  Q_D( CollectionFetchJob );
144  d->init();
145 
146  Q_ASSERT( !cols.isEmpty() );
147  if ( cols.size() == 1 ) {
148  d->mBase = Collection(cols.first());
149  } else {
150  foreach(Collection::Id id, cols)
151  d->mBaseList.append(Collection(id));
152  }
153  d->mType = type;
154 }
155 
156 CollectionFetchJob::~CollectionFetchJob()
157 {
158 }
159 
160 Akonadi::Collection::List CollectionFetchJob::collections() const
161 {
162  Q_D( const CollectionFetchJob );
163 
164  return d->mCollections;
165 }
166 
167 void CollectionFetchJob::doStart()
168 {
169  Q_D( CollectionFetchJob );
170 
171  if ( !d->mBaseList.isEmpty() ) {
172  if ( d->mType == Recursive ) {
173  // Because doStart starts several subjobs and @p cols could contain descendants of
174  // other elements in the list, if type is Recusrive, we could end up with duplicates in the result.
175  // To fix this we require an initial fetch of @p cols with Base and RetrieveAncestors,
176  // Iterate over that result removing intersections and then perform the Recursive fetch on
177  // the remainder.
178  d->mBasePrefetch = true;
179  // No need to connect to the collectionsReceived signal here. This job is internal. The
180  // result needs to be filtered through filterDescendants before it is useful.
181  new CollectionFetchJob( d->mBaseList, NonOverlappingRoots, this );
182  } else if ( d->mType == NonOverlappingRoots ) {
183  foreach ( const Collection &col, d->mBaseList ) {
184  // No need to connect to the collectionsReceived signal here. This job is internal. The (aggregated)
185  // result needs to be filtered through filterDescendants before it is useful.
186  CollectionFetchJob *subJob = new CollectionFetchJob( col, Base, this );
187  subJob->fetchScope().setAncestorRetrieval( Akonadi::CollectionFetchScope::All );
188  }
189  } else {
190  foreach ( const Collection &col, d->mBaseList ) {
191  CollectionFetchJob *subJob = new CollectionFetchJob( col, d->mType, this );
192  connect( subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List)));
193  subJob->setFetchScope( fetchScope() );
194  }
195  }
196  return;
197  }
198 
199  if ( !d->mBase.isValid() && d->mBase.remoteId().isEmpty() ) {
200  setError( Unknown );
201  setErrorText( i18n( "Invalid collection given." ) );
202  emitResult();
203  return;
204  }
205 
206  QByteArray command = d->newTag();
207  if ( !d->mBase.isValid() ) {
208  if ( CollectionUtils::hasValidHierarchicalRID( d->mBase ) )
209  command += " HRID";
210  else
211  command += " " AKONADI_CMD_RID;
212  }
213  if ( d->mScope.includeUnsubscribed() )
214  command += " LIST ";
215  else
216  command += " LSUB ";
217 
218  if ( d->mBase.isValid() )
219  command += QByteArray::number( d->mBase.id() );
220  else if ( CollectionUtils::hasValidHierarchicalRID( d->mBase ) )
221  command += '(' + ProtocolHelper::hierarchicalRidToByteArray( d->mBase ) + ')';
222  else
223  command += ImapParser::quote( d->mBase.remoteId().toUtf8() );
224 
225  command += ' ';
226  switch ( d->mType ) {
227  case Base:
228  command += "0 (";
229  break;
230  case FirstLevel:
231  command += "1 (";
232  break;
233  case Recursive:
234  command += "INF (";
235  break;
236  default:
237  Q_ASSERT( false );
238  }
239 
240  QList<QByteArray> filter;
241  if ( !d->mScope.resource().isEmpty() ) {
242  filter.append( "RESOURCE" );
243  // FIXME: Does this need to be quoted??
244  filter.append( d->mScope.resource().toUtf8() );
245  }
246 
247  if ( !d->mScope.contentMimeTypes().isEmpty() ) {
248  filter.append( "MIMETYPE" );
249  QList<QByteArray> mts;
250  foreach ( const QString &mt, d->mScope.contentMimeTypes() )
251  // FIXME: Does this need to be quoted??
252  mts.append( mt.toUtf8() );
253  filter.append( '(' + ImapParser::join( mts, " " ) + ')' );
254  }
255 
256  QList<QByteArray> options;
257  if ( d->mScope.includeStatistics() ) {
258  options.append( "STATISTICS" );
259  options.append( "true" );
260  }
261  if ( d->mScope.ancestorRetrieval() != CollectionFetchScope::None ) {
262  options.append( "ANCESTORS" );
263  switch ( d->mScope.ancestorRetrieval() ) {
264  case CollectionFetchScope::None:
265  options.append( "0" );
266  break;
267  case CollectionFetchScope::Parent:
268  options.append( "1" );
269  break;
270  case CollectionFetchScope::All:
271  options.append( "INF" );
272  break;
273  default:
274  Q_ASSERT( false );
275  }
276  }
277 
278  command += ImapParser::join( filter, " " ) + ") (" + ImapParser::join( options, " " ) + ")\n";
279  d->writeData( command );
280 }
281 
282 void CollectionFetchJob::doHandleResponse( const QByteArray & tag, const QByteArray & data )
283 {
284  Q_D( CollectionFetchJob );
285 
286  if ( d->mBasePrefetch || d->mType == NonOverlappingRoots )
287  return;
288 
289  if ( tag == "*" ) {
290  Collection collection;
291  ProtocolHelper::parseCollection( data, collection );
292  if ( !collection.isValid() )
293  return;
294 
295  collection.d_ptr->resetChangeLog();
296  d->mCollections.append( collection );
297  d->mPendingCollections.append( collection );
298  if ( !d->mEmitTimer->isActive() )
299  d->mEmitTimer->start();
300  return;
301  }
302  kDebug() << "Unhandled server response" << tag << data;
303 }
304 
305 void CollectionFetchJob::setResource(const QString & resource)
306 {
307  Q_D( CollectionFetchJob );
308 
309  d->mScope.setResource( resource );
310 }
311 
312 static Collection::List filterDescendants( const Collection::List &list )
313 {
314  Collection::List result;
315 
316  QVector<QList<Collection::Id> > ids;
317  foreach( const Collection &collection, list ) {
318  QList<Collection::Id> ancestors;
319  Collection parent = collection.parentCollection();
320  ancestors << parent.id();
321  if ( parent != Collection::root() ) {
322  while ( parent.parentCollection() != Collection::root() ) {
323  parent = parent.parentCollection();
324  QList<Collection::Id>::iterator i = qLowerBound( ancestors.begin(), ancestors.end(), parent.id() );
325  ancestors.insert( i, parent.id() );
326  }
327  }
328  ids << ancestors;
329  }
330 
331  QSet<Collection::Id> excludeList;
332  foreach ( const Collection &collection, list ) {
333  int i = 0;
334  foreach( const QList<Collection::Id> &ancestors, ids ) {
335  if ( qBinaryFind( ancestors, collection.id() ) != ancestors.end() ) {
336  excludeList.insert( list.at( i ).id() );
337  }
338  ++i;
339  }
340  }
341 
342  foreach ( const Collection &collection, list ) {
343  if ( !excludeList.contains( collection.id() ) )
344  result.append( collection );
345  }
346 
347  return result;
348 }
349 
350 void CollectionFetchJob::slotResult(KJob * job)
351 {
352  Q_D( CollectionFetchJob );
353 
354  CollectionFetchJob *list = qobject_cast<CollectionFetchJob*>( job );
355  Q_ASSERT( job );
356  if ( d->mBasePrefetch ) {
357  d->mBasePrefetch = false;
358  const Collection::List roots = list->collections();
359  Job::slotResult( job );
360  Q_ASSERT( !hasSubjobs() );
361  if ( !job->error() ) {
362  foreach ( const Collection &col, roots ) {
363  CollectionFetchJob *subJob = new CollectionFetchJob( col, d->mType, this );
364  connect( subJob, SIGNAL(collectionsReceived(Akonadi::Collection::List)), SLOT(subJobCollectionReceived(Akonadi::Collection::List)) );
365  subJob->setFetchScope( fetchScope() );
366  }
367  }
368  // No result yet.
369  } else if ( d->mType == NonOverlappingRoots ) {
370  d->mPrefetchList += list->collections();
371  Job::slotResult( job );
372  if ( !job->error() && !hasSubjobs() ) {
373  const Collection::List result = filterDescendants( d->mPrefetchList );
374  d->mPendingCollections += result;
375  d->mCollections = result;
376  d->flushIterativeResult();
377  emitResult();
378  }
379  } else {
380  // We need to tell the subjob to emit its collectionsReceived signal before
381  // the result signal is emitted. That will populate my mPendingCollections
382  // which will be flushed by calling emitResult which will cause
383  // CollectionFetchJobPrivate::timeout to be called.
384  list->d_func()->flushIterativeResult();
385  d->mCollections += list->collections();
386  // Pending collections should have already been emitted by listening to (and flushing) the job.
387  Job::slotResult( job );
388  if ( !job->error() && !hasSubjobs() )
389  emitResult();
390  }
391 }
392 
393 void CollectionFetchJob::includeUnsubscribed(bool include)
394 {
395  Q_D( CollectionFetchJob );
396 
397  d->mScope.setIncludeUnsubscribed( include );
398 }
399 
400 void CollectionFetchJob::includeStatistics(bool include)
401 {
402  Q_D( CollectionFetchJob );
403 
404  d->mScope.setIncludeStatistics( include );
405 }
406 
407 void CollectionFetchJob::setFetchScope( const CollectionFetchScope &scope )
408 {
409  Q_D( CollectionFetchJob );
410  d->mScope = scope;
411 }
412 
413 CollectionFetchScope& CollectionFetchJob::fetchScope()
414 {
415  Q_D( CollectionFetchJob );
416  return d->mScope;
417 }
418 
419 #include "collectionfetchjob.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Sep 24 2012 09:06:25 by doxygen 1.8.1.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

akonadi

Skip menu "akonadi"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Modules
  • Related Pages

kdepimlibs-4.9.1 API Reference

Skip menu "kdepimlibs-4.9.1 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal