You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/kmail/kmacctcachedimap.cpp

480 lines
17 KiB

/**
* kmacctcachedimap.cpp
*
* Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
* Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of this program with any edition of
* the Qt library by Trolltech AS, Norway (or with modified versions
* of Qt that use the same license as Qt), and distribute linked
* combinations including the two. You must obey the GNU General
* Public License in all respects for all of the code used other than
* Qt. If you modify this file, you may extend this exception to
* your version of the file, but you are not obligated to do so. If
* you do not wish to do so, delete this exception statement from
* your version.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "kmacctcachedimap.h"
using KMail::SieveConfig;
#include "kmfoldertree.h"
#include "kmfoldermgr.h"
#include "kmfiltermgr.h"
#include "kmfoldercachedimap.h"
#include "kmmainwin.h"
#include "accountmanager.h"
using KMail::AccountManager;
#include "progressmanager.h"
#include <kio/passdlg.h>
#include <kio/scheduler.h>
#include <kio/slave.h>
#include <kdebug.h>
#include <kstandarddirs.h>
#include <kapplication.h>
#include <kconfig.h>
#include <qstylesheet.h>
KMAcctCachedImap::KMAcctCachedImap( AccountManager* aOwner,
const QString& aAccountName, uint id )
: KMail::ImapAccountBase( aOwner, aAccountName, id ), mFolder( 0 ),
mAnnotationCheckPassed(false),
mGroupwareType( GroupwareKolab ),
mSentCustomLoginCommand(false)
{
// Never EVER set this for the cached IMAP account
mAutoExpunge = false;
}
//-----------------------------------------------------------------------------
KMAcctCachedImap::~KMAcctCachedImap()
{
killAllJobsInternal( true );
}
//-----------------------------------------------------------------------------
QString KMAcctCachedImap::type() const
{
return "cachedimap";
}
void KMAcctCachedImap::init() {
ImapAccountBase::init();
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::pseudoAssign( const KMAccount * a ) {
killAllJobs( true );
if (mFolder)
{
mFolder->setContentState(KMFolderCachedImap::imapNoInformation);
mFolder->setSubfolderState(KMFolderCachedImap::imapNoInformation);
}
ImapAccountBase::pseudoAssign( a );
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::setImapFolder(KMFolderCachedImap *aFolder)
{
mFolder = aFolder;
mFolder->setImapPath( "/" );
mFolder->setAccount( this );
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::setAutoExpunge( bool /*aAutoExpunge*/ )
{
// Never EVER set this for the cached IMAP account
mAutoExpunge = false;
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::killAllJobs( bool disconnectSlave )
{
//kdDebug(5006) << "killAllJobs: disconnectSlave=" << disconnectSlave << " " << mapJobData.count() << " jobs in map." << endl;
QValueList<KMFolderCachedImap*> folderList = killAllJobsInternal( disconnectSlave );
for( QValueList<KMFolderCachedImap*>::Iterator it = folderList.begin(); it != folderList.end(); ++it ) {
KMFolderCachedImap *fld = *it;
fld->resetSyncState();
fld->setContentState(KMFolderCachedImap::imapNoInformation);
fld->setSubfolderState(KMFolderCachedImap::imapNoInformation);
fld->sendFolderComplete(false);
}
}
//-----------------------------------------------------------------------------
// Common between killAllJobs and the destructor - which shouldn't call sendFolderComplete
QValueList<KMFolderCachedImap*> KMAcctCachedImap::killAllJobsInternal( bool disconnectSlave )
{
// Make list of folders to reset. This must be done last, since folderComplete
// can trigger the next queued mail check already.
QValueList<KMFolderCachedImap*> folderList;
QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
for (; it != mapJobData.end(); ++it) {
if ((*it).parent)
folderList << static_cast<KMFolderCachedImap*>((*it).parent->storage());
// Kill the job - except if it's the one that already died and is calling us
if ( !it.key()->error() && mSlave ) {
it.key()->kill();
mSlave = 0; // killing a job, kills the slave
}
}
mapJobData.clear();
// Clear the joblist. Make SURE to stop the job emitting "finished"
for( QPtrListIterator<CachedImapJob> it( mJobList ); it.current(); ++it )
it.current()->setPassiveDestructor( true );
KMAccount::deleteFolderJobs();
if ( disconnectSlave && mSlave ) {
KIO::Scheduler::disconnectSlave( mSlave );
mSlave = 0;
}
return folderList;
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::cancelMailCheck()
{
// Make list of folders to reset, like in killAllJobs
QValueList<KMFolderCachedImap*> folderList;
QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
for (; it != mapJobData.end(); ++it) {
if ( (*it).cancellable && (*it).parent )
folderList << static_cast<KMFolderCachedImap*>((*it).parent->storage());
}
// Kill jobs
ImapAccountBase::cancelMailCheck();
// Reset sync states and emit folderComplete, this is important for
// KMAccount::checkingMail() to be reset, in case we restart checking mail later.
for( QValueList<KMFolderCachedImap*>::Iterator it = folderList.begin(); it != folderList.end(); ++it ) {
KMFolderCachedImap *fld = *it;
fld->resetSyncState();
fld->setContentState(KMFolderCachedImap::imapNoInformation);
fld->setSubfolderState(KMFolderCachedImap::imapNoInformation);
fld->sendFolderComplete(false);
}
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::killJobsForItem(KMFolderTreeItem * fti)
{
QMap<KIO::Job *, jobData>::Iterator it = mapJobData.begin();
while (it != mapJobData.end())
{
if (it.data().parent == fti->folder())
{
killAllJobs();
break;
}
else ++it;
}
}
// Reimplemented from ImapAccountBase because we only check one folder at a time
void KMAcctCachedImap::slotCheckQueuedFolders()
{
mMailCheckFolders.clear();
mMailCheckFolders.append( mFoldersQueuedForChecking.front() );
mFoldersQueuedForChecking.pop_front();
if ( mFoldersQueuedForChecking.isEmpty() )
disconnect( this, SIGNAL( finishedCheck( bool, CheckStatus ) ),
this, SLOT( slotCheckQueuedFolders() ) );
kmkernel->acctMgr()->singleCheckMail(this, true);
mMailCheckFolders.clear();
}
void KMAcctCachedImap::processNewMail( bool /*interactive*/ )
{
assert( mFolder ); // George says "better to crash then lose mail"
if ( mMailCheckFolders.isEmpty() )
processNewMail( mFolder, true );
else {
KMFolder* f = mMailCheckFolders.front();
mMailCheckFolders.pop_front();
processNewMail( static_cast<KMFolderCachedImap *>( f->storage() ), false );
}
}
void KMAcctCachedImap::processNewMail( KMFolderCachedImap* folder,
bool recurse )
{
assert( folder ); // George says "better to crash then lose mail"
// This should never be set for a cached IMAP account
mAutoExpunge = false;
mCountLastUnread = 0;
mUnreadBeforeCheck.clear();
// stop sending noops during sync, that will keep the connection open
mNoopTimer.stop();
// reset namespace todo
if ( folder == mFolder ) {
QStringList nsToList = namespaces()[PersonalNS];
QStringList otherNSToCheck = namespaces()[OtherUsersNS];
otherNSToCheck += namespaces()[SharedNS];
for ( QStringList::Iterator it = otherNSToCheck.begin();
it != otherNSToCheck.end(); ++it ) {
if ( (*it).isEmpty() ) {
// empty namespaces are included in the "normal" listing
// as the folders are created under the root folder
nsToList += *it;
}
}
folder->setNamespacesToList( nsToList );
}
Q_ASSERT( !mMailCheckProgressItem );
mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
"MailCheck" + QString::number( id() ),
QStyleSheet::escape( folder->label() ), // will be changed immediately in serverSync anyway
QString::null,
true, // can be cancelled
useSSL() || useTLS() );
connect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
this, SLOT( slotProgressItemCanceled( KPIM::ProgressItem* ) ) );
folder->setAccount(this);
connect(folder, SIGNAL(folderComplete(KMFolderCachedImap*, bool)),
this, SLOT(postProcessNewMail(KMFolderCachedImap*, bool)));
folder->serverSync( recurse );
}
void KMAcctCachedImap::postProcessNewMail( KMFolderCachedImap* folder, bool )
{
mNoopTimer.start( 60000 ); // send a noop every minute to avoid "connection broken" errors
disconnect(folder, SIGNAL(folderComplete(KMFolderCachedImap*, bool)),
this, SLOT(postProcessNewMail(KMFolderCachedImap*, bool)));
mMailCheckProgressItem->setComplete();
mMailCheckProgressItem = 0;
if ( folder == mFolder ) {
// We remove everything from the deleted folders list after a full sync.
// Even if it fails (no permission), because on the next sync we want the folder to reappear,
// instead of the user being stuck with "can't delete" every time.
// And we do it for _all_ deleted folders, even those that were deleted on the server in the first place (slotListResult).
// Otherwise this might have side effects much later (e.g. when regaining permissions to a folder we could see before)
#if 0 // this opens a race: delete a folder during a sync (after the sync checked that folder), and it'll be forgotten...
mDeletedFolders.clear();
#endif
mPreviouslyDeletedFolders.clear();
}
KMail::ImapAccountBase::postProcessNewMail();
}
void KMAcctCachedImap::addUnreadMsgCount( const KMFolderCachedImap *folder,
int countUnread )
{
if ( folder->imapPath() != "/INBOX/" ) {
// new mail in INBOX is processed with KMAccount::processNewMsg() and
// therefore doesn't need to be counted here
const QString folderId = folder->folder()->idString();
int newInFolder = countUnread;
if ( mUnreadBeforeCheck.find( folderId ) != mUnreadBeforeCheck.end() )
newInFolder -= mUnreadBeforeCheck[folderId];
if ( newInFolder > 0 )
addToNewInFolder( folderId, newInFolder );
}
mCountUnread += countUnread;
}
void KMAcctCachedImap::addLastUnreadMsgCount( const KMFolderCachedImap *folder,
int countLastUnread )
{
mUnreadBeforeCheck[folder->folder()->idString()] = countLastUnread;
mCountLastUnread += countLastUnread;
}
//
//
// read/write config
//
//
void KMAcctCachedImap::readConfig( /*const*/ KConfig/*Base*/ & config ) {
ImapAccountBase::readConfig( config );
// Apparently this method is only ever called once (from KMKernel::init) so this is ok
mPreviouslyDeletedFolders = config.readListEntry( "deleted-folders" );
mDeletedFolders.clear(); // but just in case...
const QStringList oldPaths = config.readListEntry( "renamed-folders-paths" );
const QStringList newNames = config.readListEntry( "renamed-folders-names" );
QStringList::const_iterator it = oldPaths.begin();
QStringList::const_iterator nameit = newNames.begin();
for( ; it != oldPaths.end() && nameit != newNames.end(); ++it, ++nameit ) {
addRenamedFolder( *it, QString::null, *nameit );
}
mGroupwareType = (GroupwareType)config.readNumEntry( "groupwareType", GroupwareKolab );
}
void KMAcctCachedImap::writeConfig( KConfig/*Base*/ & config ) /*const*/ {
ImapAccountBase::writeConfig( config );
config.writeEntry( "deleted-folders", mDeletedFolders + mPreviouslyDeletedFolders );
config.writeEntry( "renamed-folders-paths", mRenamedFolders.keys() );
const QValueList<RenamedFolder> values = mRenamedFolders.values();
QStringList lstNames;
QValueList<RenamedFolder>::const_iterator it = values.begin();
for ( ; it != values.end() ; ++it )
lstNames.append( (*it).mNewName );
config.writeEntry( "renamed-folders-names", lstNames );
config.writeEntry( "groupwareType", mGroupwareType );
}
void KMAcctCachedImap::invalidateIMAPFolders()
{
invalidateIMAPFolders( mFolder );
}
void KMAcctCachedImap::invalidateIMAPFolders( KMFolderCachedImap* folder )
{
if( !folder || !folder->folder() )
return;
folder->setAccount(this);
QStringList strList;
QValueList<QGuardedPtr<KMFolder> > folderList;
kmkernel->dimapFolderMgr()->createFolderList( &strList, &folderList,
folder->folder()->child(), QString::null,
false );
QValueList<QGuardedPtr<KMFolder> >::Iterator it;
mCountLastUnread = 0;
mUnreadBeforeCheck.clear();
for( it = folderList.begin(); it != folderList.end(); ++it ) {
KMFolder *f = *it;
if( f && f->folderType() == KMFolderTypeCachedImap ) {
KMFolderCachedImap *cfolder = static_cast<KMFolderCachedImap*>(f->storage());
// This invalidates the folder completely
cfolder->setUidValidity("INVALID");
cfolder->writeUidCache();
processNewMailSingleFolder( f );
}
}
folder->setUidValidity("INVALID");
folder->writeUidCache();
processNewMailSingleFolder( folder->folder() );
}
//-----------------------------------------------------------------------------
void KMAcctCachedImap::addDeletedFolder( KMFolder* folder )
{
if ( !folder || folder->folderType() != KMFolderTypeCachedImap )
return;
KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(folder->storage());
addDeletedFolder( storage->imapPath() );
kdDebug(5006) << k_funcinfo << storage->imapPath() << endl;
// Add all child folders too
if( folder->child() ) {
KMFolderNode *node = folder->child()->first();
while( node ) {
if( !node->isDir() ) {
addDeletedFolder( static_cast<KMFolder*>( node ) ); // recurse
}
node = folder->child()->next();
}
}
}
void KMAcctCachedImap::addDeletedFolder( const QString& imapPath )
{
mDeletedFolders << imapPath;
}
QStringList KMAcctCachedImap::deletedFolderPaths( const QString& subFolderPath ) const
{
QStringList lst;
for ( QStringList::const_iterator it = mDeletedFolders.begin(); it != mDeletedFolders.end(); ++it ) {
if ( (*it).startsWith( subFolderPath ) )
// We must reverse the order, so that sub sub sub folders are deleted first
lst.prepend( *it );
}
for ( QStringList::const_iterator it = mPreviouslyDeletedFolders.begin(); it != mPreviouslyDeletedFolders.end(); ++it ) {
if ( (*it).startsWith( subFolderPath ) )
lst.prepend( *it );
}
kdDebug(5006) << "KMAcctCachedImap::deletedFolderPaths for " << subFolderPath << " returning: " << lst << endl;
Q_ASSERT( !lst.isEmpty() );
return lst;
}
bool KMAcctCachedImap::isDeletedFolder( const QString& subFolderPath ) const
{
return mDeletedFolders.find( subFolderPath ) != mDeletedFolders.end();
}
bool KMAcctCachedImap::isPreviouslyDeletedFolder( const QString& subFolderPath ) const
{
return mPreviouslyDeletedFolders.find( subFolderPath ) != mPreviouslyDeletedFolders.end();
}
void KMAcctCachedImap::removeDeletedFolder( const QString& subFolderPath )
{
mDeletedFolders.remove( subFolderPath );
mPreviouslyDeletedFolders.remove( subFolderPath );
}
void KMAcctCachedImap::addRenamedFolder( const QString& subFolderPath, const QString& oldLabel, const QString& newName )
{
mRenamedFolders.insert( subFolderPath, RenamedFolder( oldLabel, newName ) );
}
void KMAcctCachedImap::removeRenamedFolder( const QString& subFolderPath )
{
mRenamedFolders.remove( subFolderPath );
}
void KMAcctCachedImap::slotProgressItemCanceled( ProgressItem* )
{
bool abortConnection = !mSlaveConnected;
killAllJobs( abortConnection );
if ( abortConnection ) {
// If we were trying to connect, tell kmfoldercachedimap so that it moves on
emit connectionResult( KIO::ERR_USER_CANCELED, QString::null );
}
}
FolderStorage* const KMAcctCachedImap::rootFolder() const
{
return mFolder;
}
QString KMAcctCachedImap::renamedFolder( const QString& imapPath ) const
{
QMap<QString, RenamedFolder>::ConstIterator renit = mRenamedFolders.find( imapPath );
if ( renit != mRenamedFolders.end() )
return (*renit).mNewName;
return QString::null;
}
#include "kmacctcachedimap.moc"