[Kde-pim] [PATCH#2] imap resource: Support multiple IDLE folders.

Rüdiger Sonderfeld ruediger at c-plusplus.de
Tue Oct 23 11:24:20 BST 2012


This is version 2 of my multiple IDLE patch. I fixed the minor style issues and 
changed the rootRemoteId according to Andras suggestions.

I also added a configuration dialog and made the maxPoolSize of SessionPool 
adjustable at runtime.

Regards,
Rüdiger

-- >8 --

Currently the imap resource only uses IDLE for the /INBOX folder.
However having several inboxes or important folders is a common
use case and the imap resource should be capable of supporting it.

This patch adds support for multiple IDLE folders.  The single
IdleRidPath is replaced by IdleBoxes containing URLs to remote IMAP
folders.  The helper functions url2collection and collection2url are
used to convert from Akonadi::Collections to URLs and vice versa.  The
ImapResources class then creates an ImapIdleManagers for each
URL.  This requires one connection per watched folder!

IdleFolderDialog implements a setup dialog for this feature.  It can
be called from the regular serversetup dialog.  There currently is no
special implementation for mobile devices.  The warning text regarding
the "one connection per watched folder" problem has to be improved.

Signed-off-by: Rüdiger Sonderfeld <ruediger at c-plusplus.de>
---
 resources/imap/CMakeLists.txt                      |    2 +
 resources/imap/idlefolderdialog.cpp                |  257 
++++++++++++++++++++
 resources/imap/idlefolderdialog.h                  |   70 ++++++
 resources/imap/idlefolderdialogview.ui             |   58 +++++
 resources/imap/imapresource.cpp                    |   81 ++++--
 resources/imap/imapresource.h                      |    4 +-
 resources/imap/imapresource.kcfg                   |    8 +-
 resources/imap/resourcestate.cpp                   |   38 ++-
 resources/imap/resourcestate.h                     |    2 +-
 resources/imap/resourcestateinterface.h            |    2 +-
 resources/imap/resourcetask.cpp                    |    4 +-
 resources/imap/resourcetask.h                      |    2 +-
 resources/imap/retrievecollectionstask.cpp         |    2 +-
 resources/imap/sessionpool.cpp                     |   32 ++-
 resources/imap/sessionpool.h                       |    4 +
 resources/imap/setupserver.cpp                     |   34 +++
 resources/imap/setupserver.h                       |    1 +
 resources/imap/setupserverview_desktop.ui          |    7 +
 resources/imap/tests/dummyresourcestate.cpp        |    4 +-
 resources/imap/tests/dummyresourcestate.h          |    2 +-
 .../imap/tests/testretrievecollectionstask.cpp     |   14 +-
 resources/imap/tests/testsessionpool.cpp           |    5 +
 22 files changed, 578 insertions(+), 55 deletions(-)
 create mode 100644 resources/imap/idlefolderdialog.cpp
 create mode 100644 resources/imap/idlefolderdialog.h
 create mode 100644 resources/imap/idlefolderdialogview.ui

diff --git a/resources/imap/CMakeLists.txt b/resources/imap/CMakeLists.txt
index 7626144..2c84e17 100644
--- a/resources/imap/CMakeLists.txt
+++ b/resources/imap/CMakeLists.txt
@@ -53,6 +53,7 @@ endif( NOT IMAPRESOURCE_NO_SOLID)
 ########### next target ###############
 
 set( akonadi_imap_resource_SRCS
+  idlefolderdialog.cpp
   imapidlemanager.cpp
   imapresource.cpp
   resourcestate.cpp
@@ -70,6 +71,7 @@ else (KDEPIM_MOBILE_UI)
 kde4_add_ui_files(akonadi_imap_resource_SRCS setupserverview_desktop.ui)
 endif (KDEPIM_MOBILE_UI)
 kde4_add_ui_files(akonadi_imap_resource_SRCS serverinfo.ui)
+kde4_add_ui_files(akonadi_imap_resource_SRCS idlefolderdialogview.ui)
 
 kcfg_generate_dbus_interface( ${CMAKE_CURRENT_SOURCE_DIR}/imapresource.kcfg 
org.kde.Akonadi.Imap.Settings )
 qt4_add_dbus_adaptor( akonadi_imap_resource_SRCS
diff --git a/resources/imap/idlefolderdialog.cpp 
b/resources/imap/idlefolderdialog.cpp
new file mode 100644
index 0000000..0064fa7
--- /dev/null
+++ b/resources/imap/idlefolderdialog.cpp
@@ -0,0 +1,257 @@
+/* -*- C++ -*-
+ *
+ *  Copyright (c) 2012 Rüdiger Sonderfeld <ruediger at c-plusplus.de>
+ *
+ *  This library is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at 
your
+ *  option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful, but 
WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ *  License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this library; see the file COPYING.LIB.  If not, write to the
+ *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
MA
+ *  02110-1301, USA.
+ */
+
+#include "idlefolderdialog.h"
+
+#include "settings.h"
+#include "imapaccount.h"
+#include "sessionuiproxy.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtGui/QStandardItemModel>
+#include <QtGui/QBoxLayout>
+#include <QtGui/QCheckBox>
+#include <QtGui/QLabel>
+#include <QtGui/QTreeView>
+
+#include <kimap/session.h>
+#include <kimap/loginjob.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+
+#include "ui_idlefolderdialogview.h"
+
+IdleFolderDialog::IdleFolderDialog( QWidget *parent )
+  : KDialog( parent ),
+    m_session( 0x0 ),
+    m_ui( new Ui::IdleFolderDialogView ),
+    m_model( new QStandardItemModel( this ) )
+{
+  setModal( true );
+  setButtons( Ok | Cancel | Reset );
+
+  setButtonText( Reset, i18nc( "@action:button", "Reload &List" ) );
+  enableButton( Reset, false );
+  connect( this, SIGNAL(user1Clicked()),
+           this, SLOT(onReloadRequested()) );
+
+  m_ui->setupUi( mainWidget() );
+
+  m_ui->folderView->setModel( m_model );
+  m_ui->enableAutoInbox->setChecked( Settings::self()->autoWatchInbox() );
+
+  connect( m_model, SIGNAL(itemChanged(QStandardItem*)),
+           this, SLOT(onItemChanged(QStandardItem*)) );
+}
+
+IdleFolderDialog::~IdleFolderDialog()
+{
+}
+
+void IdleFolderDialog::connectAccount( const ImapAccount &account,
+                                       const QString &password )
+{
+  m_session = new KIMAP::Session( account.server(), account.port(), this );
+  m_session->setUiProxy( SessionUiProxy::Ptr( new SessionUiProxy ) );
+
+  KIMAP::LoginJob *login = new KIMAP::LoginJob( m_session );
+  login->setUserName( account.userName() );
+  login->setPassword( password );
+  login->setEncryptionMode( account.encryptionMode() );
+  login->setAuthenticationMode( account.authenticationMode() );
+
+  connect( login, SIGNAL(result(KJob*)),
+           this, SLOT(onLoginDone(KJob*)) );
+  login->start();
+}
+
+void IdleFolderDialog::onLoginDone( KJob *job )
+{
+  if ( !job->error() ) {
+    onReloadRequested();
+  }
+}
+
+void IdleFolderDialog::onReloadRequested()
+{
+  if ( !m_session
+    || m_session->state() != KIMAP::Session::Authenticated ) {
+    kWarning() << "SubscriptionDialog - got no connection";
+    enableButton( Reset, true );
+    return;
+  }
+
+  enableButton( Reset, false );
+  m_model->clear();
+
+  m_ui->enableAutoInbox->setChecked( Settings::self()->autoWatchInbox() );
+
+  KIMAP::ListJob *list = new KIMAP::ListJob( m_session );
+  list->setOption(KIMAP::ListJob::IncludeUnsubscribed);
+  connect( list, 
SIGNAL(mailBoxesReceived(QList<KIMAP::MailBoxDescriptor>,QList<QList<QByteArray> 
>)),
+           this, 
SLOT(onMailBoxesReceived(QList<KIMAP::MailBoxDescriptor>,QList<QList<QByteArray> 
>)) );
+  connect( list, SIGNAL(result(KJob*)), this, SLOT(onReloadDone(KJob*)) );
+  list->start();
+}
+
+namespace
+{
+  QStandardItem *findItem( QStandardItem *parentItem, const QString &text ) {
+    Q_ASSERT( parentItem );
+    const int rows = parentItem->rowCount();
+    for( int i = 0; i < rows; ++i ) {
+      QStandardItem *child = parentItem->child( i );
+      if( !child ) {
+        return 0x0;
+      }
+      else if( child->text() == text ) {
+        return child;
+      }
+    }
+    return 0x0;
+  }
+}
+
+void IdleFolderDialog::onMailBoxesReceived( const 
QList<KIMAP::MailBoxDescriptor> &mailBoxes,
+                                            const QList< QList<QByteArray> > 
&flags )
+{
+  const int numberOfMailBoxes = mailBoxes.size();
+  for( int i = 0; i < numberOfMailBoxes; ++i ) {
+    KIMAP::MailBoxDescriptor mailBox = mailBoxes[i];
+
+    const bool isCheckable = !flags[i].contains( "\\noselect" );
+
+    const QString separator = mailBox.separator;
+    Q_ASSERT( separator.size() == 1 ); // that's what the spec says
+    const QStringList pathParts = mailBox.name.split( separator );
+
+    QString parentPath;
+    QString currentPath;
+    const int numberOfPath = pathParts.size();
+    QStandardItem *parentItem = m_model->invisibleRootItem();
+    for( int j = 0; j < numberOfPath; ++j ) {
+      const QString path = pathParts.at( j );
+      currentPath += separator + path;
+
+      QStandardItem *item = findItem( parentItem, path );
+      if( !item ) {
+        item = new QStandardItem( path );
+        item->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
+        item->setData( currentPath.mid( 1 ), PathRole );
+        kDebug() << "Path " << currentPath.mid( 1 );
+
+        if( Settings::self()->idleBoxes().contains( Settings::self()-
>rootRemoteId() + currentPath.mid( 1 ) ) ) {
+          item->setCheckState( Qt::Checked );
+          item->setData( Qt::Checked, InitialStateRole );
+        }
+        parentItem->appendRow( item );
+      }
+
+      if( j == numberOfPath - 1 ) {
+        item->setCheckable( isCheckable );
+      }
+
+      parentPath = currentPath;
+      parentItem = item;
+    }
+  }
+}
+
+void IdleFolderDialog::onReloadDone( KJob *job )
+{
+  Q_UNUSED( job );
+  enableButton( Reset, true );
+}
+
+namespace
+{
+  QList<QStandardItem*> checkedItems( QStandardItem *root )
+  {
+    QList<QStandardItem*> items;
+    const int rows = root->rowCount();
+    for( int i = 0; i < rows; ++i ) {
+      QStandardItem *child = root->child( i );
+      if( child ) {
+        if( child->checkState() != Qt::Unchecked ) {
+          items << child;
+        }
+        if( child->hasChildren() ) {
+          items.append( checkedItems( child ) );
+        }
+      }
+    }
+    return items;
+  }
+}
+
+void IdleFolderDialog::onItemChanged( QStandardItem *item )
+{
+  QFont font = item->font();
+  font.setBold( item->checkState() != item->data( InitialStateRole ).toInt() 
);
+  item->setFont( font );
+}
+
+void IdleFolderDialog::slotButtonClicked( int button )
+{
+  if ( button == KDialog::Ok ) {
+    applyChanges();
+    accept();
+  } else {
+    KDialog::slotButtonClicked( button );
+  }
+}
+
+void IdleFolderDialog::applyChanges()
+{
+  const QList<QStandardItem*> items = checkedItems( m_model-
>invisibleRootItem() );
+
+  if( items.size() > warnAfterNIdleFolders ) {
+    const int answer = KMessageBox::warningContinueCancel( 0x0,
+                                                           i18n( "You are 
trying to watch %1 folders. "
+                                                                 "IMAP IDLE 
requires one open connection "
+                                                                 "for each 
watched folder.  This can cause "
+                                                                 "significant 
performance issues for the "
+                                                                 "server and 
result in connections being "
+                                                                 "dropped or 
even user bans. "
+                                                                 "We 
recommend to contact the server "
+                                                                 
"administrator before watching more than %2 "
+                                                                 "folders at 
a time.",
+                                                                 
items.size(), warnAfterNIdleFolders ),
+                                                           i18n( "Too many 
folders watched!" ),
+                                                           
KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
+                                                           
"warning_folder_watch_disabled" );
+    if( answer != KMessageBox::Continue ) {
+      return;
+    }
+  }
+
+  QStringList idleFolders;
+  Q_FOREACH( QStandardItem *item, items ) {
+    idleFolders << ( Settings::self()->rootRemoteId() + item->data( PathRole 
).toString() );
+  }
+  Settings::self()->setIdleBoxes( idleFolders );
+  Settings::self()->setAutoWatchInbox( m_ui->enableAutoInbox->isChecked() );
+
+  //TODO call imapresource startIdle();
+}
+
+#include "idlefolderdialog.moc"
diff --git a/resources/imap/idlefolderdialog.h 
b/resources/imap/idlefolderdialog.h
new file mode 100644
index 0000000..a8b004c
--- /dev/null
+++ b/resources/imap/idlefolderdialog.h
@@ -0,0 +1,70 @@
+/* -*- C++ -*-
+ *
+ *  Copyright (c) 2012 Rüdiger Sonderfeld <ruediger at c-plusplus.de>
+ *
+ *  This library is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or (at 
your
+ *  option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful, but 
WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
+ *  License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this library; see the file COPYING.LIB.  If not, write to the
+ *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
MA
+ *  02110-1301, USA.
+ */
+
+#ifndef IDLEFOLDERDIALOG_H
+#define IDLEFOLDERDIALOG_H
+
+#include <KDE/KDialog>
+
+#include <kimap/listjob.h>
+
+class QStandardItemModel;
+class QStandardItem;
+class ImapAccount;
+
+namespace Ui
+{
+class IdleFolderDialogView;
+}
+
+class IdleFolderDialog : public KDialog
+{
+  Q_OBJECT
+public:
+  static const int warnAfterNIdleFolders = 4;
+  enum Roles {
+    InitialStateRole = Qt::UserRole + 1,
+    PathRole
+  };
+
+  explicit IdleFolderDialog( QWidget *parent = 0 );
+  ~IdleFolderDialog();
+
+  void connectAccount( const ImapAccount &account, const QString &password );
+
+private slots:
+  void onLoginDone( KJob *job );
+  void onReloadRequested();
+  void onMailBoxesReceived( const QList<KIMAP::MailBoxDescriptor> &mailBoxes,
+                            const QList< QList<QByteArray> > &flags );
+  void onReloadDone( KJob *job );
+  void onItemChanged( QStandardItem *item );
+  void slotButtonClicked( int button );
+
+private:
+  void applyChanges();
+
+  KIMAP::Session *m_session;
+
+  Ui::IdleFolderDialogView *m_ui;
+  QStandardItemModel *m_model;
+};
+
+#endif
diff --git a/resources/imap/idlefolderdialogview.ui 
b/resources/imap/idlefolderdialogview.ui
new file mode 100644
index 0000000..4df67d7
--- /dev/null
+++ b/resources/imap/idlefolderdialogview.ui
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IdleFolderDialogView</class>
+ <widget class="QWidget" name="IdleFolderDialogView">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>296</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Watch Folders:</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeView" name="folderView">
+     <attribute name="headerVisible">
+      <bool>false</bool>
+     </attribute>
+    </widget>
+   </item>
+   <item>
+    <widget class="QCheckBox" name="enableAutoInbox">
+     <property name="toolTip">
+      <string>If set the inbox folder is automatically watched. Default is 
true.</string>
+     </property>
+     <property name="text">
+      <string>Automatically watch /INBOX</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="warningLabel">
+     <property name="text">
+      <string>Warning: Don't watch too many folders... TODO!</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/resources/imap/imapresource.cpp b/resources/imap/imapresource.cpp
index 096f890..de2908b 100644
--- a/resources/imap/imapresource.cpp
+++ b/resources/imap/imapresource.cpp
@@ -91,8 +91,7 @@ using namespace Akonadi;
 
 ImapResource::ImapResource( const QString &id )
   : ResourceBase( id ),
-    m_pool( new SessionPool( 2, this ) ),
-    m_idle( 0 ),
+    m_pool( new SessionPool( Settings::self()->idleBoxes().size()+1, this ) 
),
     m_fastSync( false )
 {
   if ( name() == identifier() ) {
@@ -480,8 +479,7 @@ void ImapResource::doSetOnline(bool online)
     Q_FOREACH(ResourceTask* task, m_taskList)
       task->kill();
     m_taskList.clear();
-    delete m_idle;
-    m_idle = 0;
+    clearIdle();
   } else if ( online && !m_pool->isConnected() ) {
     scheduleConnectionAttempt();
   }
@@ -519,44 +517,73 @@ void ImapResource::reconnect()
 
 // 
----------------------------------------------------------------------------------
 
+void ImapResource::clearIdle() {
+  Q_FOREACH( ImapIdleManager *im, m_idle ) {
+    delete im;
+  }
+  m_idle.clear();
+}
+
 void ImapResource::startIdleIfNeeded()
 {
-  if ( !m_idle ) {
+  if ( m_idle.isEmpty() ) {
     startIdle();
   }
 }
 
+namespace
+{
+  Collection url2collection(const QUrl &url)
+  {
+    if( url.scheme() != "imap" && url.scheme() != "imaps" )
+      return Collection();
+
+    // Add authority part (e.g., imap://foo@example.org)
+    Collection c, p;
+    p.setParentCollection( Collection::root() );
+    c.setParentCollection( p );
+    c.setRemoteId( Settings::self()->rootRemoteId() );
+    p = c;
+
+    // Add a collection item for each subdir
+    QStringList path = url.path().split('/');
+    if( !path.isEmpty() && path.first().isEmpty() )
+      path.removeFirst();
+
+    Q_FOREACH( const QString &s, path ) {
+      c.setParentCollection( p );
+      c.setRemoteId( '/' + s );
+      p = c;
+    }
+
+    return c;
+  }
+}
+
 void ImapResource::startIdle()
 {
-  delete m_idle;
-  m_idle = 0;
+  clearIdle();
 
   if ( !m_pool->serverCapabilities().contains( "IDLE" ) )
     return;
 
-  const QStringList ridPath = Settings::self()->idleRidPath();
-  if ( ridPath.size() < 2 )
+  const QStringList boxes = Settings::self()->idleBoxes();
+  if( boxes.isEmpty() )
     return;
+  Q_FOREACH( const QString &box, boxes ) {
+    Collection c = url2collection(QUrl(box));
 
-  Collection c, p;
-  p.setParentCollection( Collection::root() );
-  for ( int i = ridPath.size() - 1; i > 0; --i ) {
-    p.setRemoteId( ridPath.at( i ) );
-    c.setParentCollection( p );
-    p = c;
-  }
-  c.setRemoteId( ridPath.first() );
-
-  Akonadi::CollectionFetchScope scope;
-  scope.setResource( identifier() );
-  scope.setAncestorRetrieval( Akonadi::CollectionFetchScope::All );
+    Akonadi::CollectionFetchScope scope;
+    scope.setResource( identifier() );
+    scope.setAncestorRetrieval( Akonadi::CollectionFetchScope::All );
 
-  Akonadi::CollectionFetchJob *fetch
-    = new Akonadi::CollectionFetchJob( c, Akonadi::CollectionFetchJob::Base, 
this );
-  fetch->setFetchScope( scope );
+    Akonadi::CollectionFetchJob *fetch
+      = new Akonadi::CollectionFetchJob( c, 
Akonadi::CollectionFetchJob::Base, this );
+    fetch->setFetchScope( scope );
 
-  connect( fetch, SIGNAL(result(KJob*)),
-           this, SLOT(onIdleCollectionFetchDone(KJob*)) );
+    connect( fetch, SIGNAL(result(KJob*)),
+             this, SLOT(onIdleCollectionFetchDone(KJob*)) );
+  }
 }
 
 void ImapResource::onIdleCollectionFetchDone( KJob *job )
@@ -570,7 +597,7 @@ void ImapResource::onIdleCollectionFetchDone( KJob *job )
       return;
 
     ResourceStateInterface::Ptr state = ::ResourceState::createIdleState( 
this, c );
-    m_idle = new ImapIdleManager( state, m_pool, this );
+    m_idle.push_back( new ImapIdleManager( state, m_pool, this ) );
 
   } else {
     kWarning() << "CollectionFetch for idling failed."
diff --git a/resources/imap/imapresource.h b/resources/imap/imapresource.h
index da1ccfe..fdbae80 100644
--- a/resources/imap/imapresource.h
+++ b/resources/imap/imapresource.h
@@ -122,11 +122,13 @@ private:
   void queueTask( ResourceTask *task );
   bool needsNetwork() const;
 
+  void clearIdle();
+
   friend class ImapIdleManager;
 
   SessionPool *m_pool;
   QList<ResourceTask*> m_taskList;
-  ImapIdleManager *m_idle;
+  QList<ImapIdleManager*> m_idle;
   bool m_fastSync;
 };
 
diff --git a/resources/imap/imapresource.kcfg 
b/resources/imap/imapresource.kcfg
index f3bfd84..1ef67f0 100644
--- a/resources/imap/imapresource.kcfg
+++ b/resources/imap/imapresource.kcfg
@@ -71,8 +71,12 @@
     </entry>
   </group>
   <group name="idle">
-    <entry name="IdleRidPath" type="StringList">
-      <label>RID path to the mailbox to watch for changes</label>
+    <entry name="IdleBoxes" type="StringList">
+      <label>URL to mailboxes to watch for changes</label>
+    </entry>
+    <entry name="AutoWatchInbox" type="Bool">
+      <label>Automatically watch /INBOX</label>
+      <default>true</default>
     </entry>
   </group>
   <group name="siever">
diff --git a/resources/imap/resourcestate.cpp 
b/resources/imap/resourcestate.cpp
index 7aa42d8..7605eb0 100644
--- a/resources/imap/resourcestate.cpp
+++ b/resources/imap/resourcestate.cpp
@@ -297,17 +297,39 @@ QString ResourceState::mailBoxForCollection( const 
Akonadi::Collection &collecti
   return mailbox;
 }
 
-void ResourceState::setIdleCollection( const Akonadi::Collection &collection 
)
-{
-  QStringList ridPath;
+namespace
+{
+  QString collection2url( const Akonadi::Collection &collection )
+  {
+    QStringList ridPath;
+
+    Akonadi::Collection curCol = collection;
+    while ( curCol != Akonadi::Collection::root() && 
!curCol.remoteId().isEmpty() ) {
+      QString remoteId = curCol.remoteId();
+      // Remove one / if the next id starts with a / and the current ends 
with /
+      if( !ridPath.isEmpty() && ridPath.front()[0] == '/' && 
remoteId.endsWith('/') )
+        remoteId.chop(1);
+      ridPath.push_front( remoteId );
+      curCol = curCol.parentCollection();
+    }
 
-  Akonadi::Collection curCol = collection;
-  while ( curCol != Akonadi::Collection::root() && 
!curCol.remoteId().isEmpty() ) {
-    ridPath.append( curCol.remoteId() );
-    curCol = curCol.parentCollection();
+    return ridPath.join("");
   }
+}
+
+void ResourceState::addIdleCollection( const Akonadi::Collection &collection 
)
+{
+  if( !Settings::self()->autoWatchInbox() )
+    return;
+
+  const QString url( collection2url( collection ) );
+  if( Settings::self()->idleBoxes().contains( url ) )
+    return;
+
+  QStringList boxes = Settings::self()->idleBoxes();
+  boxes << url;
 
-  Settings::self()->setIdleRidPath( ridPath );
+  Settings::self()->setIdleBoxes( boxes );
   Settings::self()->writeConfig();
 }
 
diff --git a/resources/imap/resourcestate.h b/resources/imap/resourcestate.h
index d795c44..485c0dc 100644
--- a/resources/imap/resourcestate.h
+++ b/resources/imap/resourcestate.h
@@ -107,7 +107,7 @@ public:
   virtual QString rootRemoteId() const;
   virtual QString mailBoxForCollection( const Akonadi::Collection 
&collection, bool showWarnings = true ) const;
 
-  virtual void setIdleCollection( const Akonadi::Collection &collection );
+  virtual void addIdleCollection( const Akonadi::Collection &collection );
   virtual void applyCollectionChanges( const Akonadi::Collection &collection 
);
 
   virtual void collectionAttributesRetrieved( const Akonadi::Collection 
&collection );
diff --git a/resources/imap/resourcestateinterface.h 
b/resources/imap/resourcestateinterface.h
index 45024e6..f531d68 100644
--- a/resources/imap/resourcestateinterface.h
+++ b/resources/imap/resourcestateinterface.h
@@ -61,7 +61,7 @@ public:
   virtual QString rootRemoteId() const = 0;
   virtual QString mailBoxForCollection( const Akonadi::Collection 
&collection, bool showWarnings = true ) const = 0;
 
-  virtual void setIdleCollection( const Akonadi::Collection &collection ) = 
0;
+  virtual void addIdleCollection( const Akonadi::Collection &collection ) = 
0;
   virtual void applyCollectionChanges( const Akonadi::Collection &collection 
) = 0;
 
   virtual void collectionAttributesRetrieved( const Akonadi::Collection 
&collection ) = 0;
diff --git a/resources/imap/resourcetask.cpp b/resources/imap/resourcetask.cpp
index c97b413..db1f852 100644
--- a/resources/imap/resourcetask.cpp
+++ b/resources/imap/resourcetask.cpp
@@ -216,9 +216,9 @@ QString ResourceTask::mailBoxForCollection( const 
Akonadi::Collection &collectio
   return m_resource->mailBoxForCollection( collection );
 }
 
-void ResourceTask::setIdleCollection( const Akonadi::Collection &collection )
+void ResourceTask::addIdleCollection( const Akonadi::Collection &collection )
 {
-  m_resource->setIdleCollection( collection );
+  m_resource->addIdleCollection( collection );
 }
 
 void ResourceTask::applyCollectionChanges( const Akonadi::Collection 
&collection )
diff --git a/resources/imap/resourcetask.h b/resources/imap/resourcetask.h
index 3893a5d..49bcaad 100644
--- a/resources/imap/resourcetask.h
+++ b/resources/imap/resourcetask.h
@@ -83,7 +83,7 @@ protected:
   QString rootRemoteId() const;
   QString mailBoxForCollection( const Akonadi::Collection &collection ) 
const;
 
-  void setIdleCollection( const Akonadi::Collection &collection );
+  void addIdleCollection( const Akonadi::Collection &collection );
   void applyCollectionChanges( const Akonadi::Collection &collection );
 
   void collectionAttributesRetrieved( const Akonadi::Collection &collection 
);
diff --git a/resources/imap/retrievecollectionstask.cpp 
b/resources/imap/retrievecollectionstask.cpp
index 5afebe5..cea5618 100644
--- a/resources/imap/retrievecollectionstask.cpp
+++ b/resources/imap/retrievecollectionstask.cpp
@@ -162,7 +162,7 @@ void RetrieveCollectionsTask::onMailBoxesReceived( const 
QList< KIMAP::MailBoxDe
         Akonadi::EntityDisplayAttribute *attr = 
c.attribute<Akonadi::EntityDisplayAttribute>( 
Akonadi::Collection::AddIfMissing );
         attr->setDisplayName( i18n( "Inbox" ) );
         attr->setIconName( "mail-folder-inbox" );
-        setIdleCollection( c );
+        addIdleCollection( c );
       }
 
       // If the folder is the user top-level folder, mark it as well, even 
although it is not officially noted in the RFC
diff --git a/resources/imap/sessionpool.cpp b/resources/imap/sessionpool.cpp
index d3ea149..23dac91 100644
--- a/resources/imap/sessionpool.cpp
+++ b/resources/imap/sessionpool.cpp
@@ -41,6 +41,7 @@ qint64 SessionPool::m_requestCounter = 0;
 SessionPool::SessionPool( int maxPoolSize, QObject *parent )
   : QObject( parent ),
     m_maxPoolSize( maxPoolSize ),
+    m_reduceSessionPoolBy( 0 ),
     m_account( 0 ),
     m_passwordRequester( 0 ),
     m_initialConnectDone( false ),
@@ -151,7 +152,13 @@ void SessionPool::releaseSession( KIMAP::Session *session 
)
 {
   if ( !m_reservedPool.isEmpty() && m_reservedPool.contains( session ) ) {
     m_reservedPool.removeAll( session );
-    m_unusedPool << session;
+    if( m_reduceSessionPoolBy > 0 ) { // pool size has to be reduced.
+      --m_reduceSessionPoolBy;
+      session->deleteLater();
+    }
+    else {
+      m_unusedPool << session;
+    }
   }
 }
 
@@ -170,6 +177,29 @@ QList<KIMAP::MailBoxDescriptor> 
SessionPool::serverNamespaces() const
   return m_namespaces;
 }
 
+int SessionPool::maxPoolSize() const
+{
+  return m_maxPoolSize;
+}
+
+void SessionPool::setMaxPoolSize( int maxPoolSize )
+{
+  if( m_unusedPool.size() + m_reservedPool.size() + m_connectingPool.size() > 
maxPoolSize ) {
+    // try to close as many unused sessions as possible
+    while( !m_unusedPool.isEmpty()
+          && m_unusedPool.size() + m_reservedPool.size() + 
m_connectingPool.size() > maxPoolSize) {
+      killSession( m_unusedPool.first(), LogoutSession );
+      m_unusedPool.removeFirst();
+    }
+    // if that's not enough then add no new sessions to the unused pool until 
maxPoolSize is reached
+    m_reduceSessionPoolBy = m_unusedPool.size() + m_reservedPool.size() + 
m_connectingPool.size() - maxPoolSize;
+  }
+  else {
+    m_reduceSessionPoolBy = 0;
+  }
+  m_maxPoolSize = maxPoolSize;
+}
+
 void SessionPool::killSession( KIMAP::Session *session, SessionTermination 
termination )
 {
   QObject::disconnect( session, 
SIGNAL(stateChanged(KIMAP::Session::State,KIMAP::Session::State)),
diff --git a/resources/imap/sessionpool.h b/resources/imap/sessionpool.h
index 0b22619..245c3af 100644
--- a/resources/imap/sessionpool.h
+++ b/resources/imap/sessionpool.h
@@ -84,6 +84,9 @@ public:
   QStringList serverCapabilities() const;
   QList<KIMAP::MailBoxDescriptor> serverNamespaces() const;
 
+  int maxPoolSize() const;
+  void setMaxPoolSize( int maxPoolSize );
+
 signals:
   void connectionLost( KIMAP::Session *session );
 
@@ -111,6 +114,7 @@ private:
   static qint64 m_requestCounter;
 
   int m_maxPoolSize;
+  int m_reduceSessionPoolBy;
   ImapAccount *m_account;
   PasswordRequesterInterface *m_passwordRequester;
   KIMAP::SessionUiProxy::Ptr m_sessionUiProxy;
diff --git a/resources/imap/setupserver.cpp b/resources/imap/setupserver.cpp
index 0b9866a..8ffd4a7 100644
--- a/resources/imap/setupserver.cpp
+++ b/resources/imap/setupserver.cpp
@@ -64,6 +64,7 @@
 
 #include "imapaccount.h"
 #include "subscriptiondialog.h"
+#include "idlefolderdialog.h"
 
 #ifdef KDEPIM_MOBILE_UI
 #include "ui_setupserverview_mobile.h"
@@ -173,6 +174,15 @@ SetupServer::SetupServer( ImapResource *parentResource, 
WId parent )
   connect( m_ui->subscriptionEnabled, SIGNAL(toggled(bool)), this, 
SLOT(slotSubcriptionCheckboxChanged()) );
   connect( m_ui->subscriptionButton, SIGNAL(pressed()), 
SLOT(slotManageSubscriptions()) );
 
+  if( m_parentResource->serverCapabilities().contains( "IDLE" ) ) {
+    m_ui->idleButton->setEnabled( true );
+    connect( m_ui->idleButton, SIGNAL(pressed()), 
SLOT(slotManageIdleFolders()) );
+  }
+  else {
+    m_ui->idleButton->setToolTip( i18n( "The server does not support IMAP 
IDLE." ) );
+    m_ui->idleButton->setEnabled( false );
+  }
+
   connect( m_ui->managesieveCheck, SIGNAL(toggled(bool)),
            SLOT(slotEnableWidgets()) );
   connect( m_ui->sameConfigCheck, SIGNAL(toggled(bool)),
@@ -574,6 +584,30 @@ void SetupServer::slotManageSubscriptions()
   m_ui->subscriptionEnabled->setChecked( account.isSubscriptionEnabled() );
 }
 
+void SetupServer::slotManageIdleFolders()
+{
+  ImapAccount account;
+  account.setServer( m_ui->imapServer->text() );
+  account.setPort( m_ui->portSpin->value() );
+
+  account.setUserName( m_ui->userName->text() );
+  account.setSubscriptionEnabled( m_ui->subscriptionEnabled->isChecked() );
+
+  account.setEncryptionMode(
+    static_cast<KIMAP::LoginJob::EncryptionMode>( m_ui->safeImapGroup-
>checkedId() )
+  );
+
+  account.setAuthenticationMode( Settings::mapTransportAuthToKimap( 
getCurrentAuthMode( m_ui->authenticationCombo ) ) );
+
+  QPointer<IdleFolderDialog> idleFolders = new IdleFolderDialog( this );
+  idleFolders->setCaption( i18n( "" ) );
+  idleFolders->setWindowIcon( KIcon( "network-server" ) );
+  idleFolders->connectAccount( account, m_ui->password->text() );
+
+  idleFolders->exec();
+  delete idleFolders;
+}
+
 void SetupServer::slotShowServerInfo()
 {
   KDialog *dialog = new KDialog( this );
diff --git a/resources/imap/setupserver.h b/resources/imap/setupserver.h
index a1ac763..da87556 100644
--- a/resources/imap/setupserver.h
+++ b/resources/imap/setupserver.h
@@ -103,6 +103,7 @@ private slots:
   void slotSafetyChanged();
   void slotManageSubscriptions();
   void slotEnableWidgets();
+  void slotManageIdleFolders();
   void targetCollectionReceived( Akonadi::Collection::List collections );
   void localFolderRequestJobFinished( KJob *job );
 };
diff --git a/resources/imap/setupserverview_desktop.ui 
b/resources/imap/setupserverview_desktop.ui
index 4d0f1e8..7f4393f 100644
--- a/resources/imap/setupserverview_desktop.ui
+++ b/resources/imap/setupserverview_desktop.ui
@@ -494,6 +494,13 @@ p, li { white-space: pre-wrap; }
         </widget>
        </item>
        <item>
+        <widget class="KPushButton" name="idleButton">
+         <property name="text">
+          <string>Setup Folder Watch ...</string>
+         </property>
+        </widget>
+       </item>
+       <item>
         <spacer name="verticalSpacer_3">
          <property name="orientation">
           <enum>Qt::Vertical</enum>
diff --git a/resources/imap/tests/dummyresourcestate.cpp 
b/resources/imap/tests/dummyresourcestate.cpp
index 518bf8a..966d8c4 100644
--- a/resources/imap/tests/dummyresourcestate.cpp
+++ b/resources/imap/tests/dummyresourcestate.cpp
@@ -183,9 +183,9 @@ QString DummyResourceState::mailBoxForCollection( const 
Akonadi::Collection &col
   return collection.remoteId().mid( 1 );
 }
 
-void DummyResourceState::setIdleCollection( const Akonadi::Collection 
&collection )
+void DummyResourceState::addIdleCollection( const Akonadi::Collection 
&collection )
 {
-  recordCall( "setIdleCollection",  QVariant::fromValue( collection ) );
+  recordCall( "addIdleCollection",  QVariant::fromValue( collection ) );
 }
 
 void DummyResourceState::applyCollectionChanges( const Akonadi::Collection 
&collection )
diff --git a/resources/imap/tests/dummyresourcestate.h 
b/resources/imap/tests/dummyresourcestate.h
index eb4b2ef..77ac8c3 100644
--- a/resources/imap/tests/dummyresourcestate.h
+++ b/resources/imap/tests/dummyresourcestate.h
@@ -77,7 +77,7 @@ public:
   virtual QString rootRemoteId() const;
   virtual QString mailBoxForCollection( const Akonadi::Collection 
&collection, bool showWarnings = true ) const;
 
-  virtual void setIdleCollection( const Akonadi::Collection &collection );
+  virtual void addIdleCollection( const Akonadi::Collection &collection );
   virtual void applyCollectionChanges( const Akonadi::Collection &collection 
);
 
   virtual void collectionAttributesRetrieved( const Akonadi::Collection 
&collection );
diff --git a/resources/imap/tests/testretrievecollectionstask.cpp 
b/resources/imap/tests/testretrievecollectionstask.cpp
index c9b41bb..7472c89 100644
--- a/resources/imap/tests/testretrievecollectionstask.cpp
+++ b/resources/imap/tests/testretrievecollectionstask.cpp
@@ -72,7 +72,7 @@ private slots:
              << "S: A000003 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = false;
     isDisconnectedModeEnabled = false;
@@ -125,7 +125,7 @@ private slots:
              << "S: A000003 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = false;
     isDisconnectedModeEnabled = true;
@@ -151,7 +151,7 @@ private slots:
              << "S: A000003 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = false;
     isDisconnectedModeEnabled = false;
@@ -172,7 +172,7 @@ private slots:
              << "S: A000003 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = false;
     isDisconnectedModeEnabled = false;
@@ -206,7 +206,7 @@ private slots:
              << "S: A000004 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = true;
     isDisconnectedModeEnabled = false;
@@ -230,7 +230,7 @@ private slots:
              << "S: A000004 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = true;
     isDisconnectedModeEnabled = false;
@@ -252,7 +252,7 @@ private slots:
              << "S: A000003 OK list done";
 
     callNames.clear();
-    callNames << "setIdleCollection" << "collectionsRetrieved";
+    callNames << "addIdleCollection" << "collectionsRetrieved";
 
     isSubscriptionEnabled = false;
     isDisconnectedModeEnabled = false;
diff --git a/resources/imap/tests/testsessionpool.cpp 
b/resources/imap/tests/testsessionpool.cpp
index 43e2fc4..8fa60c3 100644
--- a/resources/imap/tests/testsessionpool.cpp
+++ b/resources/imap/tests/testsessionpool.cpp
@@ -196,6 +196,11 @@ private slots:
     SessionPool pool( 2 );
 
     QVERIFY( !pool.isConnected() );
+    QCOMPARE( pool.maxPoolSize(), 2 );
+    pool.setMaxPoolSize( 4 );
+    QCOMPARE( pool.maxPoolSize(), 4 );
+    pool.setMaxPoolSize( 2 );
+    QCOMPARE( pool.maxPoolSize(), 2 );
 
     QSignalSpy poolSpy( &pool, SIGNAL(connectDone(int,QString)) );
 
-- 
1.7.10.4

_______________________________________________
KDE PIM mailing list kde-pim at kde.org
https://mail.kde.org/mailman/listinfo/kde-pim
KDE PIM home page at http://pim.kde.org/


More information about the kde-pim mailing list