diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch) | |
tree | 67208f7c145782a7e90b123b982ca78d88cc2c87 /kmail/kmfoldercachedimap.cpp | |
download | tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmail/kmfoldercachedimap.cpp')
-rw-r--r-- | kmail/kmfoldercachedimap.cpp | 2992 |
1 files changed, 2992 insertions, 0 deletions
diff --git a/kmail/kmfoldercachedimap.cpp b/kmail/kmfoldercachedimap.cpp new file mode 100644 index 000000000..405176c4e --- /dev/null +++ b/kmail/kmfoldercachedimap.cpp @@ -0,0 +1,2992 @@ +/** + * kmfoldercachedimap.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 <errno.h> + +#include <qvaluevector.h> + +#include "kmkernel.h" +#include "kmfoldercachedimap.h" +#include "undostack.h" +#include "kmfoldermgr.h" +#include "kmacctcachedimap.h" +#include "accountmanager.h" +using KMail::AccountManager; +#include "kmailicalifaceimpl.h" +#include "kmfolder.h" +#include "kmglobal.h" +#include "acljobs.h" +#include "broadcaststatus.h" +using KPIM::BroadcastStatus; +#include "progressmanager.h" + +using KMail::CachedImapJob; +#include "imapaccountbase.h" +using KMail::ImapAccountBase; +#include "listjob.h" +using KMail::ListJob; + +#include "kmfolderseldlg.h" +#include "kmcommands.h" +#include "kmmainwidget.h" + +#include <kapplication.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kdebug.h> +#include <kconfig.h> +#include <kio/global.h> +#include <kio/scheduler.h> +#include <qbuffer.h> +#include <qbuttongroup.h> +#include <qcombobox.h> +#include <qfile.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qradiobutton.h> +#include <qvaluelist.h> +#include "annotationjobs.h" +#include "quotajobs.h" +using namespace KMail; +#include <globalsettings.h> + +#define UIDCACHE_VERSION 1 +#define MAIL_LOSS_DEBUGGING 0 + +static QString incidencesForToString( KMFolderCachedImap::IncidencesFor r ) { + switch (r) { + case KMFolderCachedImap::IncForNobody: return "nobody"; + case KMFolderCachedImap::IncForAdmins: return "admins"; + case KMFolderCachedImap::IncForReaders: return "readers"; + } + return QString::null; // can't happen +} + +static KMFolderCachedImap::IncidencesFor incidencesForFromString( const QString& str ) { + if ( str == "nobody" ) return KMFolderCachedImap::IncForNobody; + if ( str == "admins" ) return KMFolderCachedImap::IncForAdmins; + if ( str == "readers" ) return KMFolderCachedImap::IncForReaders; + return KMFolderCachedImap::IncForAdmins; // by default +} + +DImapTroubleShootDialog::DImapTroubleShootDialog( QWidget* parent, + const char* name ) + : KDialogBase( Plain, i18n( "Troubleshooting IMAP Cache" ), + Ok | Cancel, Cancel, parent, name, true ), + rc( None ) +{ + QFrame* page = plainPage(); + QVBoxLayout *topLayout = new QVBoxLayout( page, 0 ); + // spell "lose" correctly. but don't cause a fuzzy. + QString txt = i18n( "<p><b>Troubleshooting the IMAP cache.</b></p>" + "<p>If you have problems with synchronizing an IMAP " + "folder, you should first try rebuilding the index " + "file. This will take some time to rebuild, but will " + "not cause any problems.</p><p>If that is not enough, " + "you can try refreshing the IMAP cache. If you do this, " + "you will loose all your local changes for this folder " + "and all its subfolders.</p>", + "<p><b>Troubleshooting the IMAP cache.</b></p>" + "<p>If you have problems with synchronizing an IMAP " + "folder, you should first try rebuilding the index " + "file. This will take some time to rebuild, but will " + "not cause any problems.</p><p>If that is not enough, " + "you can try refreshing the IMAP cache. If you do this, " + "you will lose all your local changes for this folder " + "and all its subfolders.</p>" ); + topLayout->addWidget( new QLabel( txt, page ) ); + + QButtonGroup *group = new QButtonGroup( 0 ); + + mIndexButton = new QRadioButton( page ); + mIndexButton->setText( i18n( "Rebuild &Index" ) ); + group->insert( mIndexButton ); + topLayout->addWidget( mIndexButton ); + + QHBox *hbox = new QHBox( page ); + QLabel *scopeLabel = new QLabel( i18n( "Scope:" ), hbox ); + scopeLabel->setEnabled( false ); + mIndexScope = new QComboBox( hbox ); + mIndexScope->insertItem( i18n( "Only current folder" ) ); + mIndexScope->insertItem( i18n( "Current folder and all subfolders" ) ); + mIndexScope->insertItem( i18n( "All folders of this account" ) ); + mIndexScope->setEnabled( false ); + topLayout->addWidget( hbox ); + + mCacheButton = new QRadioButton( page ); + mCacheButton->setText( i18n( "Refresh &Cache" ) ); + group->insert( mCacheButton ); + topLayout->addWidget( mCacheButton ); + + enableButtonSeparator( true ); + + connect ( mIndexButton, SIGNAL(toggled(bool)), mIndexScope, SLOT(setEnabled(bool)) ); + connect ( mIndexButton, SIGNAL(toggled(bool)), scopeLabel, SLOT(setEnabled(bool)) ); + + connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) ); +} + +int DImapTroubleShootDialog::run() +{ + DImapTroubleShootDialog d; + d.exec(); + return d.rc; +} + +void DImapTroubleShootDialog::slotDone() +{ + rc = None; + if ( mIndexButton->isOn() ) + rc = mIndexScope->currentItem(); + else if ( mCacheButton->isOn() ) + rc = RefreshCache; + done( Ok ); +} + +KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName ) + : KMFolderMaildir( folder, aName ), + mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ), + mSubfolderState( imapNoInformation ), + mIncidencesFor( IncForAdmins ), + mIsSelected( false ), + mCheckFlags( true ), mReadOnly( false ), mAccount( NULL ), uidMapDirty( true ), + uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ), + mFoundAnIMAPDigest( false ), + mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ), + /*mHoldSyncs( false ),*/ + mFolderRemoved( false ), + mRecurse( true ), + mStatusChangedLocally( false ), mAnnotationFolderTypeChanged( false ), + mIncidencesForChanged( false ), mPersonalNamespacesCheckDone( true ), + mQuotaInfo(), mAlarmsBlocked( false ), + mRescueCommandCount( 0 ), + mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values) +{ + setUidValidity(""); + // if we fail to read a uid file but there is one, nuke it + if ( readUidCache() == -1 ) { + if ( QFile::exists( uidCacheLocation() ) ) { + KMessageBox::error( 0, + i18n( "The UID cache file for folder %1 could not be read. There " + "could be a problem with file system permission, or it is corrupted." + ).arg( folder->prettyURL() ) ); + // try to unlink it, in case it was corruped. If it couldn't be read + // because of permissions, this will fail, which is fine + unlink( QFile::encodeName( uidCacheLocation() ) ); + } + } + + mProgress = 0; +} + +KMFolderCachedImap::~KMFolderCachedImap() +{ + if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() ); +} + +void KMFolderCachedImap::reallyDoClose( const char* owner ) +{ + if( !mFolderRemoved ) { + writeUidCache(); + } + KMFolderMaildir::reallyDoClose( owner ); +} + +void KMFolderCachedImap::initializeFrom( KMFolderCachedImap* parent ) +{ + setAccount( parent->account() ); + // Now that we have an account, tell it that this folder was created: + // if this folder was just removed, then we don't really want to remove it from the server. + mAccount->removeDeletedFolder( imapPath() ); + setUserRights( parent->userRights() ); +} + +void KMFolderCachedImap::readConfig() +{ + KConfig* config = KMKernel::config(); + KConfigGroupSaver saver( config, "Folder-" + folder()->idString() ); + if( mImapPath.isEmpty() ) mImapPath = config->readEntry( "ImapPath" ); + if( QString( name() ).upper() == "INBOX" && mImapPath == "/INBOX/" ) + { + folder()->setLabel( i18n( "inbox" ) ); + // for the icon + folder()->setSystemFolder( true ); + } + mNoContent = config->readBoolEntry( "NoContent", false ); + mReadOnly = config->readBoolEntry( "ReadOnly", false ); + if ( !config->readEntry( "FolderAttributes" ).isEmpty() ) + mFolderAttributes = config->readEntry( "FolderAttributes" ); + + if ( mAnnotationFolderType != "FROMSERVER" ) { + mAnnotationFolderType = config->readEntry( "Annotation-FolderType" ); + // if there is an annotation, it has to be XML + if ( !mAnnotationFolderType.isEmpty() && !mAnnotationFolderType.startsWith( "mail" ) ) + kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML ); +// kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath ) +// << " readConfig: mAnnotationFolderType=" << mAnnotationFolderType << endl; + } + mIncidencesFor = incidencesForFromString( config->readEntry( "IncidencesFor" ) ); + mAlarmsBlocked = config->readBoolEntry( "AlarmsBlocked", false ); +// kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath ) +// << " readConfig: mIncidencesFor=" << mIncidencesFor << endl; + + mUserRights = config->readNumEntry( "UserRights", 0 ); // default is we don't know + mOldUserRights = mUserRights; + + int storageQuotaUsage = config->readNumEntry( "StorageQuotaUsage", -1 ); + int storageQuotaLimit = config->readNumEntry( "StorageQuotaLimit", -1 ); + QString storageQuotaRoot = config->readEntry( "StorageQuotaRoot", QString::null ); + if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set + mQuotaInfo.setName( "STORAGE" ); + mQuotaInfo.setRoot( storageQuotaRoot ); + + if ( storageQuotaUsage > -1 ) + mQuotaInfo.setCurrent( storageQuotaUsage ); + if ( storageQuotaLimit > -1 ) + mQuotaInfo.setMax( storageQuotaLimit ); + } + + KMFolderMaildir::readConfig(); + + mStatusChangedLocally = + config->readBoolEntry( "StatusChangedLocally", false ); + + mAnnotationFolderTypeChanged = config->readBoolEntry( "AnnotationFolderTypeChanged", false ); + mIncidencesForChanged = config->readBoolEntry( "IncidencesForChanged", false ); + if ( mImapPath.isEmpty() ) { + mImapPathCreation = config->readEntry("ImapPathCreation"); + } + + QStringList uids = config->readListEntry( "UIDSDeletedSinceLastSync" ); +#if MAIL_LOSS_DEBUGGING + kdDebug( 5006 ) << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyURL() << endl << uids << endl; +#endif + for ( QStringList::iterator it = uids.begin(); it != uids.end(); it++ ) { + mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0); + } +} + +void KMFolderCachedImap::writeConfig() +{ + // don't re-write the config of a removed folder, this has just been deleted in + // the folder manager + if ( mFolderRemoved ) + return; + + KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() ); + configGroup.writeEntry( "ImapPath", mImapPath ); + configGroup.writeEntry( "NoContent", mNoContent ); + configGroup.writeEntry( "ReadOnly", mReadOnly ); + configGroup.writeEntry( "FolderAttributes", mFolderAttributes ); + configGroup.writeEntry( "StatusChangedLocally", mStatusChangedLocally ); + if ( !mImapPathCreation.isEmpty() ) { + if ( mImapPath.isEmpty() ) { + configGroup.writeEntry( "ImapPathCreation", mImapPathCreation ); + } else { + configGroup.deleteEntry( "ImapPathCreation" ); + } + } + if ( !mDeletedUIDsSinceLastSync.isEmpty() ) { + QValueList<ulong> uids = mDeletedUIDsSinceLastSync.keys(); + QStringList uidstrings; + for( QValueList<ulong>::iterator it = uids.begin(); it != uids.end(); it++ ) { + uidstrings.append( QString::number( (*it) ) ); + } + configGroup.writeEntry( "UIDSDeletedSinceLastSync", uidstrings ); +#if MAIL_LOSS_DEBUGGING + kdDebug( 5006 ) << "WRITING OUT UIDSDeletedSinceLastSync in: " << folder( )->prettyURL( ) << endl << uidstrings << endl; +#endif + } else { + configGroup.deleteEntry( "UIDSDeletedSinceLastSync" ); + } + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); + KMFolderMaildir::writeConfig(); +} + +void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig() +{ + KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() ); + if ( !folder()->noContent() ) + { + configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged ); + configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType ); + configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged ); + configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) ); + configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked ); + configGroup.writeEntry( "UserRights", mUserRights ); + + configGroup.deleteEntry( "StorageQuotaUsage"); + configGroup.deleteEntry( "StorageQuotaRoot"); + configGroup.deleteEntry( "StorageQuotaLimit"); + + if ( mQuotaInfo.isValid() ) { + if ( mQuotaInfo.current().isValid() ) { + configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() ); + } + if ( mQuotaInfo.max().isValid() ) { + configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() ); + } + configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() ); + } + } +} + +int KMFolderCachedImap::create() +{ + int rc = KMFolderMaildir::create(); + // FIXME why the below? - till + readConfig(); + mUnreadMsgs = -1; + return rc; +} + +void KMFolderCachedImap::remove() +{ + mFolderRemoved = true; + + QString part1 = folder()->path() + "/." + dotEscape(name()); + QString uidCacheFile = part1 + ".uidcache"; + // This is the account folder of an account that was just removed + // When this happens, be sure to delete all traces of the cache + if( QFile::exists(uidCacheFile) ) + unlink( QFile::encodeName( uidCacheFile ) ); + + FolderStorage::remove(); +} + +QString KMFolderCachedImap::uidCacheLocation() const +{ + QString sLocation(folder()->path()); + if (!sLocation.isEmpty()) sLocation += '/'; + return sLocation + '.' + dotEscape(fileName()) + ".uidcache"; +} + +int KMFolderCachedImap::readUidCache() +{ + QFile uidcache( uidCacheLocation() ); + if( uidcache.open( IO_ReadOnly ) ) { + char buf[1024]; + int len = uidcache.readLine( buf, sizeof(buf) ); + if( len > 0 ) { + int cacheVersion; + sscanf( buf, "# KMail-UidCache V%d\n", &cacheVersion ); + if( cacheVersion == UIDCACHE_VERSION ) { + len = uidcache.readLine( buf, sizeof(buf) ); + if( len > 0 ) { + setUidValidity( QString::fromLocal8Bit(buf).stripWhiteSpace() ); + len = uidcache.readLine( buf, sizeof(buf) ); + if( len > 0 ) { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Reading in last uid from cache: " << QString::fromLocal8Bit(buf).stripWhiteSpace() << " in " << folder()->prettyURL() << endl; +#endif + // load the last known highest uid from the on disk cache + setLastUid( QString::fromLocal8Bit(buf).stripWhiteSpace().toULong() ); + return 0; + } + } + } + } + } + return -1; +} + +int KMFolderCachedImap::writeUidCache() +{ + if( uidValidity().isEmpty() || uidValidity() == "INVALID" ) { + // No info from the server yet, remove the file. + if( QFile::exists( uidCacheLocation() ) ) + return unlink( QFile::encodeName( uidCacheLocation() ) ); + return 0; + } +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Writing out UID cache lastuid: " << lastUid() << " in: " << folder()->prettyURL() << endl; +#endif + QFile uidcache( uidCacheLocation() ); + if( uidcache.open( IO_WriteOnly ) ) { + QTextStream str( &uidcache ); + str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl; + str << uidValidity() << endl; + str << lastUid() << endl; + uidcache.flush(); + if ( uidcache.status() == IO_Ok ) { + fsync( uidcache.handle() ); /* this is probably overkill */ + uidcache.close(); + if ( uidcache.status() == IO_Ok ) + return 0; + } + } + KMessageBox::error( 0, + i18n( "The UID cache file for folder %1 could not be written. There " + "could be a problem with file system permission." ).arg( folder()->prettyURL() ) ); + + return -1; +} + +void KMFolderCachedImap::reloadUidMap() +{ + //kdDebug(5006) << "Reloading Uid Map " << endl; + uidMap.clear(); + open("reloadUdi"); + for( int i = 0; i < count(); ++i ) { + KMMsgBase *msg = getMsgBase( i ); + if( !msg ) continue; + ulong uid = msg->UID(); + //kdDebug(5006) << "Inserting: " << i << " with uid: " << uid << endl; + uidMap.insert( uid, i ); + } + close("reloadUdi"); + uidMapDirty = false; +} + +KMMessage* KMFolderCachedImap::take(int idx) +{ + uidMapDirty = true; + rememberDeletion( idx ); + return KMFolderMaildir::take(idx); +} + +void KMFolderCachedImap::takeTemporarily( int idx ) +{ + KMFolderMaildir::take( idx ); +} + +// Add a message without clearing it's X-UID field. +int KMFolderCachedImap::addMsgInternal( KMMessage* msg, bool newMail, + int* index_return ) +{ + // Possible optimization: Only dirty if not filtered below + ulong uid = msg->UID(); + if( uid != 0 ) { + uidMapDirty = true; + } + + KMFolderOpener openThis(folder(), "KMFolderCachedImap::addMsgInternal"); + int rc = openThis.openResult(); + if ( rc ) { + kdDebug(5006) << k_funcinfo << "open: " << rc << " of folder: " << label() << endl; + return rc; + } + + // Add the message + rc = KMFolderMaildir::addMsg(msg, index_return); + + if( newMail && ( imapPath() == "/INBOX/" || ( !GlobalSettings::self()->filterOnlyDIMAPInbox() + && (userRights() <= 0 || userRights() & ACLJobs::Administer ) + && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) ) + // This is a new message. Filter it + mAccount->processNewMsg( msg ); + + return rc; +} + +/* Reimplemented from KMFolderMaildir */ +int KMFolderCachedImap::addMsg(KMMessage* msg, int* index_return) +{ + if ( !canAddMsgNow( msg, index_return ) ) return 0; + // Add it to storage + int rc = KMFolderMaildir::addMsgInternal(msg, index_return, true /*stripUID*/); + return rc; +} + +void KMFolderCachedImap::rememberDeletion( int idx ) +{ + KMMsgBase *msg = getMsgBase( idx ); + assert(msg); + long uid = msg->UID(); + assert(uid>=0); + mDeletedUIDsSinceLastSync.insert(uid, 0); + kdDebug(5006) << "Explicit delete of UID " << uid << " at index: " << idx << " in " << folder()->prettyURL() << endl; +} + +/* Reimplemented from KMFolderMaildir */ +void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet) +{ + uidMapDirty = true; + rememberDeletion( idx ); + // Remove it from disk + KMFolderMaildir::removeMsg(idx,imapQuiet); +} + +bool KMFolderCachedImap::canRemoveFolder() const { + // If this has subfolders it can't be removed + if( folder() && folder()->child() && folder()->child()->count() > 0 ) + return false; + +#if 0 + // No special condition here, so let base class decide + return KMFolderMaildir::canRemoveFolder(); +#endif + return true; +} + +/* Reimplemented from KMFolderDir */ +int KMFolderCachedImap::rename( const QString& aName, + KMFolderDir* /*aParent*/ ) +{ + QString oldName = mAccount->renamedFolder( imapPath() ); + if ( oldName.isEmpty() ) oldName = name(); + if ( aName == oldName ) + // Stupid user trying to rename it to it's old name :) + return 0; + + if( account() == 0 || imapPath().isEmpty() ) { // I don't think any of this can happen anymore + QString err = i18n("You must synchronize with the server before renaming IMAP folders."); + KMessageBox::error( 0, err ); + return -1; + } + + // Make the change appear to the user with setLabel, but we'll do the change + // on the server during the next sync. The name() is the name at the time of + // the last sync. Only rename if the new one is different. If it's the same, + // don't rename, but also make sure the rename is reset, in the case of + // A -> B -> A renames. + if ( name() != aName ) + mAccount->addRenamedFolder( imapPath(), folder()->label(), aName ); + else + mAccount->removeRenamedFolder( imapPath() ); + + folder()->setLabel( aName ); + emit nameChanged(); // for kmailicalifaceimpl + + return 0; +} + +KMFolder* KMFolderCachedImap::trashFolder() const +{ + QString trashStr = account()->trash(); + return kmkernel->dimapFolderMgr()->findIdString( trashStr ); +} + +void KMFolderCachedImap::setLastUid( ulong uid ) +{ +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Setting mLastUid to: " << uid << " in " << folder()->prettyURL() << endl; +#endif + mLastUid = uid; + if( uidWriteTimer == -1 ) + // Write in one minute + uidWriteTimer = startTimer( 60000 ); +} + +void KMFolderCachedImap::timerEvent( QTimerEvent* ) +{ + killTimer( uidWriteTimer ); + uidWriteTimer = -1; + if ( writeUidCache() == -1 ) + unlink( QFile::encodeName( uidCacheLocation() ) ); +} + +ulong KMFolderCachedImap::lastUid() +{ + return mLastUid; +} + +KMMsgBase* KMFolderCachedImap::findByUID( ulong uid ) +{ + bool mapReloaded = false; + if( uidMapDirty ) { + reloadUidMap(); + mapReloaded = true; + } + + QMap<ulong,int>::Iterator it = uidMap.find( uid ); + if( it != uidMap.end() ) { + KMMsgBase *msg = getMsgBase( *it ); +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Folder: " << folder()->prettyURL() << endl; + kdDebug(5006) << "UID " << uid << " is supposed to be in the map" << endl; + kdDebug(5006) << "UID's index is to be " << *it << endl; + kdDebug(5006) << "There is a message there? " << (msg != 0) << endl; + if ( msg ) { + kdDebug(5006) << "Its UID is: " << msg->UID() << endl; + } +#endif + + if( msg && msg->UID() == uid ) + return msg; + kdDebug(5006) << "########## Didn't find uid: " << uid << "in cache athough it's supposed to be there!" << endl; + } else { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Didn't find uid: " << uid << "in cache!" << endl; +#endif + } + // Not found by now + // if( mapReloaded ) + // Not here then + return 0; + // There could be a problem in the maps. Rebuild them and try again + reloadUidMap(); + it = uidMap.find( uid ); + if( it != uidMap.end() ) + // Since the uid map is just rebuilt, no need for the sanity check + return getMsgBase( *it ); +#if MAIL_LOSS_DEBUGGING + else + kdDebug(5006) << "Reloaded, but stil didn't find uid: " << uid << endl; +#endif + // Then it's not here + return 0; +} + +// This finds and sets the proper account for this folder if it has +// not been done +KMAcctCachedImap *KMFolderCachedImap::account() const +{ + if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) { + // Find the account + mAccount = static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( name() ) ); + } + + return mAccount; +} + +void KMFolderCachedImap::slotTroubleshoot() +{ + const int rc = DImapTroubleShootDialog::run(); + + if( rc == DImapTroubleShootDialog::RefreshCache ) { + // Refresh cache + if( !account() ) { + KMessageBox::sorry( 0, i18n("No account setup for this folder.\n" + "Please try running a sync before this.") ); + return; + } + QString str = i18n("Are you sure you want to refresh the IMAP cache of " + "the folder %1 and all its subfolders?\nThis will " + "remove all changes you have done locally to your " + "folders.").arg( label() ); + QString s1 = i18n("Refresh IMAP Cache"); + QString s2 = i18n("&Refresh"); + if( KMessageBox::warningContinueCancel( 0, str, s1, s2 ) == + KMessageBox::Continue ) + account()->invalidateIMAPFolders( this ); + } else { + // Rebuild index file + switch ( rc ) { + case DImapTroubleShootDialog::ReindexAll: + { + KMFolderCachedImap *rootStorage = dynamic_cast<KMFolderCachedImap*>( account()->rootFolder() ); + if ( rootStorage ) + rootStorage->createIndexFromContentsRecursive(); + break; + } + case DImapTroubleShootDialog::ReindexCurrent: + createIndexFromContents(); + break; + case DImapTroubleShootDialog::ReindexRecursive: + createIndexFromContentsRecursive(); + break; + default: + return; + } + KMessageBox::information( 0, i18n( "The index of this folder has been " + "recreated." ) ); + writeIndex(); + kmkernel->getKMMainWidget()->folderSelected(); + } +} + +void KMFolderCachedImap::serverSync( bool recurse ) +{ + if( mSyncState != SYNC_STATE_INITIAL ) { + if( KMessageBox::warningYesNo( 0, i18n("Folder %1 is not in initial sync state (state was %2). Do you want to reset it to initial sync state and sync anyway?" ).arg( imapPath() ).arg( mSyncState ), QString::null, i18n("Reset && Sync"), KStdGuiItem::cancel() ) == KMessageBox::Yes ) { + mSyncState = SYNC_STATE_INITIAL; + } else return; + } + + mRecurse = recurse; + assert( account() ); + + ProgressItem *progressItem = mAccount->mailCheckProgressItem(); + if ( progressItem ) { + progressItem->reset(); + progressItem->setTotalItems( 100 ); + } + mProgress = 0; + +#if 0 + if( mHoldSyncs ) { + // All done for this folder. + account()->mailCheckProgressItem()->setProgress( 100 ); + mProgress = 100; // all done + newState( mProgress, i18n("Synchronization skipped")); + mSyncState = SYNC_STATE_INITIAL; + emit folderComplete( this, true ); + return; + } +#endif + mTentativeHighestUid = 0; // reset, last sync could have been canceled + + serverSyncInternal(); +} + +QString KMFolderCachedImap::state2String( int state ) const +{ + switch( state ) { + case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL"; + case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS"; + case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES"; + case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS"; + case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS"; + case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS"; + case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES"; + case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2"; + case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS"; + case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES"; + case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES"; + case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES"; + case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES"; + case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX"; + case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS"; + case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS"; + case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS"; + case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS"; + case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS"; + case SYNC_STATE_GET_QUOTA: return "SYNC_STATE_GET_QUOTA"; + case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS"; + case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS"; + case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER"; + case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY"; + default: return "Unknown state"; + } +} + +/* + Progress calculation: each step is assigned a span. Initially the total is 100. + But if we skip a step, don't increase the progress. + This leaves more room for the step a with variable size (get_messages) + connecting 5 + getuserrights 5 + rename 5 + check_uidvalidity 5 + create_subfolders 5 + put_messages 10 (but it can take a very long time, with many messages....) + upload_flags 5 + list_subfolders 5 + list_subfolders2 0 (all local) + delete_subfolders 5 + list_messages 10 + delete_messages 10 + expunge_messages 5 + get_messages variable (remaining-5) i.e. minimum 15. + check_annotations 0 (rare) + set_annotations 0 (rare) + get_annotations 2 + set_acls 0 (rare) + get_acls 3 + + noContent folders have only a few of the above steps + (permissions, and all subfolder stuff), so its steps should be given more span + + */ + +// While the server synchronization is running, mSyncState will hold +// the state that should be executed next +void KMFolderCachedImap::serverSyncInternal() +{ + // This is used to stop processing when we're about to exit + // and the current job wasn't cancellable. + // For user-requested abort, we'll use signalAbortRequested instead. + if( kmkernel->mailCheckAborted() ) { + resetSyncState(); + emit folderComplete( this, false ); + return; + } + + //kdDebug(5006) << label() << ": " << state2String( mSyncState ) << endl; + switch( mSyncState ) { + case SYNC_STATE_INITIAL: + { + mProgress = 0; + foldersForDeletionOnServer.clear(); + newState( mProgress, i18n("Synchronizing")); + + open("cachedimap"); + if ( !noContent() ) + mAccount->addLastUnreadMsgCount( this, countUnread() ); + + // Connect to the server (i.e. prepare the slave) + ImapAccountBase::ConnectionState cs = mAccount->makeConnection(); + if ( cs == ImapAccountBase::Error ) { + // Cancelled by user, or slave can't start + // kdDebug(5006) << "makeConnection said Error, aborting." << endl; + // We stop here. We're already in SYNC_STATE_INITIAL for the next time. + newState( mProgress, i18n( "Error connecting to server %1" ).arg( mAccount->host() ) ); + close("cachedimap"); + emit folderComplete(this, false); + break; + } else if ( cs == ImapAccountBase::Connecting ) { + mAccount->setAnnotationCheckPassed( false ); + // kdDebug(5006) << "makeConnection said Connecting, waiting for signal." << endl; + newState( mProgress, i18n("Connecting to %1").arg( mAccount->host() ) ); + // We'll wait for the connectionResult signal from the account. + connect( mAccount, SIGNAL( connectionResult(int, const QString&) ), + this, SLOT( slotConnectionResult(int, const QString&) ) ); + break; + } else { + // Connected + // kdDebug(5006) << "makeConnection said Connected, proceeding." << endl; + mSyncState = SYNC_STATE_GET_USERRIGHTS; + // Fall through to next state + } + } + + + case SYNC_STATE_GET_USERRIGHTS: + //kdDebug(5006) << "===== Syncing " << ( mImapPath.isEmpty() ? label() : mImapPath ) << endl; + + mSyncState = SYNC_STATE_RENAME_FOLDER; + + if( !noContent() && mAccount->hasACLSupport() ) { + // Check the user's own rights. We do this every time in case they changed. + mOldUserRights = mUserRights; + newState( mProgress, i18n("Checking permissions")); + connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ), + this, SLOT( slotReceivedUserRights( KMFolder* ) ) ); + mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case + break; + } + + case SYNC_STATE_RENAME_FOLDER: + { + mSyncState = SYNC_STATE_CHECK_UIDVALIDITY; + // Returns the new name if the folder was renamed, empty otherwise. + bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() ); + QString newName = mAccount->renamedFolder( imapPath() ); + if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) { + newState( mProgress, i18n("Renaming folder") ); + CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this ); + connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); + connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); + job->start(); + break; + } + } + + case SYNC_STATE_CHECK_UIDVALIDITY: + mSyncState = SYNC_STATE_CREATE_SUBFOLDERS; + if( !noContent() ) { + checkUidValidity(); + break; + } + // Else carry on + + case SYNC_STATE_CREATE_SUBFOLDERS: + mSyncState = SYNC_STATE_PUT_MESSAGES; + createNewFolders(); + break; + + case SYNC_STATE_PUT_MESSAGES: + mSyncState = SYNC_STATE_UPLOAD_FLAGS; + if( !noContent() ) { + uploadNewMessages(); + break; + } + // Else carry on + case SYNC_STATE_UPLOAD_FLAGS: + mSyncState = SYNC_STATE_LIST_NAMESPACES; + if( !noContent() ) { + // We haven't downloaded messages yet, so we need to build the map. + if( uidMapDirty ) + reloadUidMap(); + // Upload flags, unless we know from the ACL that we're not allowed + // to do that or they did not change locally + if ( mUserRights <= 0 || ( mUserRights & (KMail::ACLJobs::WriteFlags ) ) ) { + if ( mStatusChangedLocally ) { + uploadFlags(); + break; + } else { + //kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl; + } + } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) { + if ( mStatusChangedLocally ) { + uploadSeenFlags(); + break; + } + } + } + // Else carry on + + case SYNC_STATE_LIST_NAMESPACES: + if ( this == mAccount->rootFolder() ) { + listNamespaces(); + break; + } + mSyncState = SYNC_STATE_LIST_SUBFOLDERS; + // Else carry on + + case SYNC_STATE_LIST_SUBFOLDERS: + newState( mProgress, i18n("Retrieving folderlist")); + mSyncState = SYNC_STATE_LIST_SUBFOLDERS2; + if( !listDirectory() ) { + mSyncState = SYNC_STATE_INITIAL; + KMessageBox::error(0, i18n("Error while retrieving the folderlist")); + } + break; + + case SYNC_STATE_LIST_SUBFOLDERS2: + mSyncState = SYNC_STATE_DELETE_SUBFOLDERS; + mProgress += 10; + newState( mProgress, i18n("Retrieving subfolders")); + listDirectory2(); + break; + + case SYNC_STATE_DELETE_SUBFOLDERS: + mSyncState = SYNC_STATE_LIST_MESSAGES; + if( !foldersForDeletionOnServer.isEmpty() ) { + newState( mProgress, i18n("Deleting folders from server")); + CachedImapJob* job = new CachedImapJob( foldersForDeletionOnServer, + CachedImapJob::tDeleteFolders, this ); + connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); + connect( job, SIGNAL( finished() ), this, SLOT( slotFolderDeletionOnServerFinished() ) ); + job->start(); + break; + } + // Not needed, the next step emits newState very quick + //newState( mProgress, i18n("No folders to delete from server")); + // Carry on + + case SYNC_STATE_LIST_MESSAGES: + mSyncState = SYNC_STATE_DELETE_MESSAGES; + if( !noContent() ) { + newState( mProgress, i18n("Retrieving message list")); + listMessages(); + break; + } + // Else carry on + + case SYNC_STATE_DELETE_MESSAGES: + mSyncState = SYNC_STATE_EXPUNGE_MESSAGES; + if( !noContent() ) { + if( deleteMessages() ) { + // Fine, we will continue with the next state + } else { + // No messages to delete, skip to GET_MESSAGES + newState( mProgress, i18n("No messages to delete...")); + mSyncState = SYNC_STATE_GET_MESSAGES; + serverSyncInternal(); + } + break; + } + // Else carry on + + case SYNC_STATE_EXPUNGE_MESSAGES: + mSyncState = SYNC_STATE_GET_MESSAGES; + if( !noContent() ) { + newState( mProgress, i18n("Expunging deleted messages")); + CachedImapJob *job = new CachedImapJob( QString::null, + CachedImapJob::tExpungeFolder, this ); + connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); + connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); + job->start(); + break; + } + // Else carry on + + case SYNC_STATE_GET_MESSAGES: + mSyncState = SYNC_STATE_HANDLE_INBOX; + if( !noContent() ) { + if( !mMsgsForDownload.isEmpty() ) { + newState( mProgress, i18n("Retrieving new messages")); + CachedImapJob *job = new CachedImapJob( mMsgsForDownload, + CachedImapJob::tGetMessage, + this ); + connect( job, SIGNAL( progress(unsigned long, unsigned long) ), + this, SLOT( slotProgress(unsigned long, unsigned long) ) ); + connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) ); + connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); + job->start(); + mMsgsForDownload.clear(); + break; + } else { + newState( mProgress, i18n("No new messages from server")); + /* There were no messages to download, but it could be that we uploaded some + which we didn't need to download again because we already knew the uid. + Now that we are sure there is nothing to download, and everything that had + to be deleted on the server has been deleted, adjust our local notion of the + highes uid seen thus far. */ + slotUpdateLastUid(); + if( mLastUid == 0 && uidWriteTimer == -1 ) { + // This is probably a new and empty folder. Write the UID cache + if ( writeUidCache() == -1 ) { + resetSyncState(); + emit folderComplete( this, false ); + return; + } + } + } + } + + // Else carry on + + case SYNC_STATE_HANDLE_INBOX: + // Wrap up the 'download emails' stage. We always end up at 95 here. + mProgress = 95; + mSyncState = SYNC_STATE_TEST_ANNOTATIONS; + + #define KOLAB_FOLDERTEST "/vendor/kolab/folder-test" + case SYNC_STATE_TEST_ANNOTATIONS: + mSyncState = SYNC_STATE_GET_ANNOTATIONS; + // The first folder with user rights to write annotations + if( !mAccount->annotationCheckPassed() && + ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) + && !imapPath().isEmpty() && imapPath() != "/" ) { + kdDebug(5006) << "Setting test attribute on folder: "<< folder()->prettyURL() << endl; + newState( mProgress, i18n("Checking annotation support")); + + KURL url = mAccount->getUrl(); + url.setPath( imapPath() ); + KMail::AnnotationList annotations; // to be set + + KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" ); + annotations.append( attr ); + + kdDebug(5006) << "Setting test attribute to "<< url << endl; + KIO::Job* job = AnnotationJobs::multiSetAnnotation( mAccount->slave(), + url, annotations ); + ImapAccountBase::jobData jd( url.url(), folder() ); + jd.cancellable = true; // we can always do so later + mAccount->insertJob(job, jd); + connect(job, SIGNAL(result(KIO::Job *)), + SLOT(slotTestAnnotationResult(KIO::Job *))); + break; + } + + case SYNC_STATE_GET_ANNOTATIONS: { +#define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type" +#define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for" +//#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/* + mSyncState = SYNC_STATE_SET_ANNOTATIONS; + + bool needToGetInitialAnnotations = false; + if ( !noContent() ) { + // for a folder we didn't create ourselves: get annotation from server + if ( mAnnotationFolderType == "FROMSERVER" ) { + needToGetInitialAnnotations = true; + mAnnotationFolderType = QString::null; + } else { + updateAnnotationFolderType(); + } + } + + // First retrieve the annotation, so that we know we have to set it if it's not set. + // On the other hand, if the user changed the contentstype, there's no need to get first. + if ( !noContent() && mAccount->hasAnnotationSupport() && + ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) { + QStringList annotations; // list of annotations to be fetched + if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() ) + annotations << KOLAB_FOLDERTYPE; + if ( !mIncidencesForChanged ) + annotations << KOLAB_INCIDENCESFOR; + if ( !annotations.isEmpty() ) { + newState( mProgress, i18n("Retrieving annotations")); + KURL url = mAccount->getUrl(); + url.setPath( imapPath() ); + AnnotationJobs::MultiGetAnnotationJob* job = + AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations ); + ImapAccountBase::jobData jd( url.url(), folder() ); + jd.cancellable = true; + mAccount->insertJob(job, jd); + + connect( job, SIGNAL(annotationResult(const QString&, const QString&, bool)), + SLOT(slotAnnotationResult(const QString&, const QString&, bool)) ); + connect( job, SIGNAL(result(KIO::Job *)), + SLOT(slotGetAnnotationResult(KIO::Job *)) ); + break; + } + } + } // case + case SYNC_STATE_SET_ANNOTATIONS: + + mSyncState = SYNC_STATE_SET_ACLS; + if ( !noContent() && mAccount->hasAnnotationSupport() && + ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) { + newState( mProgress, i18n("Setting annotations")); + KURL url = mAccount->getUrl(); + url.setPath( imapPath() ); + KMail::AnnotationList annotations; // to be set + if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) { + KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared", mAnnotationFolderType ); + annotations.append( attr ); + kdDebug(5006) << "Setting folder-type annotation for " << label() << " to " << mAnnotationFolderType << endl; + } + if ( mIncidencesForChanged ) { + const QString val = incidencesForToString( mIncidencesFor ); + KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared", val ); + annotations.append( attr ); + kdDebug(5006) << "Setting incidences-for annotation for " << label() << " to " << val << endl; + } + if ( !annotations.isEmpty() ) { + KIO::Job* job = + AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations ); + ImapAccountBase::jobData jd( url.url(), folder() ); + jd.cancellable = true; // we can always do so later + mAccount->insertJob(job, jd); + + connect(job, SIGNAL(annotationChanged( const QString&, const QString&, const QString& ) ), + SLOT( slotAnnotationChanged( const QString&, const QString&, const QString& ) )); + connect(job, SIGNAL(result(KIO::Job *)), + SLOT(slotSetAnnotationResult(KIO::Job *))); + break; + } + } + + case SYNC_STATE_SET_ACLS: + mSyncState = SYNC_STATE_GET_ACLS; + + if( !noContent() && mAccount->hasACLSupport() && + ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) { + bool hasChangedACLs = false; + ACLList::ConstIterator it = mACLList.begin(); + for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) { + hasChangedACLs = (*it).changed; + } + if ( hasChangedACLs ) { + newState( mProgress, i18n("Setting permissions")); + KURL url = mAccount->getUrl(); + url.setPath( imapPath() ); + KIO::Job* job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url, mACLList ); + ImapAccountBase::jobData jd( url.url(), folder() ); + mAccount->insertJob(job, jd); + + connect(job, SIGNAL(result(KIO::Job *)), + SLOT(slotMultiSetACLResult(KIO::Job *))); + connect(job, SIGNAL(aclChanged( const QString&, int )), + SLOT(slotACLChanged( const QString&, int )) ); + break; + } + } + + case SYNC_STATE_GET_ACLS: + mSyncState = SYNC_STATE_GET_QUOTA; + + if( !noContent() && mAccount->hasACLSupport() ) { + newState( mProgress, i18n( "Retrieving permissions" ) ); + mAccount->getACL( folder(), mImapPath ); + connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), + this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); + break; + } + case SYNC_STATE_GET_QUOTA: + // Continue with the subfolders + mSyncState = SYNC_STATE_FIND_SUBFOLDERS; + if( !noContent() && mAccount->hasQuotaSupport() ) { + newState( mProgress, i18n("Getting quota information")); + KURL url = mAccount->getUrl(); + url.setPath( imapPath() ); + KIO::Job* job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url ); + ImapAccountBase::jobData jd( url.url(), folder() ); + mAccount->insertJob(job, jd); + connect( job, SIGNAL( storageQuotaResult( const QuotaInfo& ) ), + SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) ); + connect( job, SIGNAL(result(KIO::Job *)), + SLOT(slotQuotaResult(KIO::Job *)) ); + break; + } + case SYNC_STATE_FIND_SUBFOLDERS: + { + mProgress = 98; + newState( mProgress, i18n("Updating cache file")); + + mSyncState = SYNC_STATE_SYNC_SUBFOLDERS; + mSubfoldersForSync.clear(); + mCurrentSubfolder = 0; + if( folder() && folder()->child() ) { + KMFolderNode *node = folder()->child()->first(); + while( node ) { + if( !node->isDir() ) { + KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage()); + // Only sync folders that have been accepted by the server + if ( !storage->imapPath().isEmpty() + // and that were not just deleted from it + && !foldersForDeletionOnServer.contains( storage->imapPath() ) ) { + mSubfoldersForSync << storage; + } else { + kdDebug(5006) << "Do not add " << storage->label() + << " to synclist" << endl; + } + } + node = folder()->child()->next(); + } + } + + // All done for this folder. + mProgress = 100; // all done + newState( mProgress, i18n("Synchronization done")); + KURL url = mAccount->getUrl(); + url.setPath( imapPath() ); + kmkernel->iCalIface().folderSynced( folder(), url ); + } + + if ( !mRecurse ) // "check mail for this folder" only + mSubfoldersForSync.clear(); + + // Carry on + case SYNC_STATE_SYNC_SUBFOLDERS: + { + if( mCurrentSubfolder ) { + disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), + this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); + mCurrentSubfolder = 0; + } + + if( mSubfoldersForSync.isEmpty() ) { + mSyncState = SYNC_STATE_INITIAL; + mAccount->addUnreadMsgCount( this, countUnread() ); // before closing + close("cachedimap"); + emit folderComplete( this, true ); + } else { + mCurrentSubfolder = mSubfoldersForSync.front(); + mSubfoldersForSync.pop_front(); + connect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), + this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); + + //kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl; + assert( !mCurrentSubfolder->imapPath().isEmpty() ); + mCurrentSubfolder->setAccount( account() ); + bool recurse = mCurrentSubfolder->noChildren() ? false : true; + mCurrentSubfolder->serverSync( recurse ); + } + } + break; + + default: + kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state " + << mSyncState << endl; + } +} + +/* Connected to the imap account's connectionResult signal. + Emitted when the slave connected or failed to connect. +*/ +void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString& errorMsg ) +{ + disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ), + this, SLOT( slotConnectionResult(int, const QString&) ) ); + if ( !errorCode ) { + // Success + mSyncState = SYNC_STATE_GET_USERRIGHTS; + mProgress += 5; + serverSyncInternal(); + } else { + // Error (error message already shown by the account) + newState( mProgress, KIO::buildErrorString( errorCode, errorMsg )); + emit folderComplete(this, false); + } +} + +/* find new messages (messages without a UID) */ +QValueList<unsigned long> KMFolderCachedImap::findNewMessages() +{ + QValueList<unsigned long> result; + for( int i = 0; i < count(); ++i ) { + KMMsgBase *msg = getMsgBase( i ); + if( !msg ) continue; /* what goes on if getMsg() returns 0? */ + if ( msg->UID() == 0 ) + result.append( msg->getMsgSerNum() ); + } + return result; +} + +/* Upload new messages to server */ +void KMFolderCachedImap::uploadNewMessages() +{ + QValueList<unsigned long> newMsgs = findNewMessages(); + if( !newMsgs.isEmpty() ) { + if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) { + newState( mProgress, i18n("Uploading messages to server")); + CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this ); + connect( job, SIGNAL( progress( unsigned long, unsigned long) ), + this, SLOT( slotPutProgress(unsigned long, unsigned long) ) ); + connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); + job->start(); + return; + } else { + KMCommand *command = rescueUnsyncedMessages(); + connect( command, SIGNAL( completed( KMCommand * ) ), + this, SLOT( serverSyncInternal() ) ); + } + } else { // nothing to upload + if ( mUserRights != mOldUserRights && (mOldUserRights & KMail::ACLJobs::Insert) + && !(mUserRights & KMail::ACLJobs::Insert) ) { + // write access revoked + KMessageBox::information( 0, i18n("<p>Your access rights to folder <b>%1</b> have been restricted, " + "it will no longer be possible to add messages to this folder.</p>").arg( folder()->prettyURL() ), + i18n("Acces rights revoked"), "KMailACLRevocationNotification" ); + } + } + newState( mProgress, i18n("No messages to upload to server")); + serverSyncInternal(); +} + +/* Progress info during uploadNewMessages */ +void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total ) +{ + // (going from mProgress to mProgress+10) + int progressSpan = 10; + newState( mProgress + (progressSpan * done) / total, QString::null ); + if ( done == total ) // we're done + mProgress += progressSpan; +} + +/* Upload message flags to server */ +void KMFolderCachedImap::uploadFlags() +{ + if ( !uidMap.isEmpty() ) { + mStatusFlagsJobs = 0; + newState( mProgress, i18n("Uploading status of messages to server")); + + // FIXME DUPLICATED FROM KMFOLDERIMAP + QMap< QString, QStringList > groups; + //open(); //already done + for( int i = 0; i < count(); ++i ) { + KMMsgBase* msg = getMsgBase( i ); + if( !msg || msg->UID() == 0 ) + // Either not a valid message or not one that is on the server yet + continue; + + QString flags = KMFolderImap::statusToFlags(msg->status(), mPermanentFlags); + // Collect uids for each typem of flags. + QString uid; + uid.setNum( msg->UID() ); + groups[flags].append(uid); + } + QMapIterator< QString, QStringList > dit; + for( dit = groups.begin(); dit != groups.end(); ++dit ) { + QCString flags = dit.key().latin1(); + QStringList sets = KMFolderImap::makeSets( (*dit), true ); + mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap.... + // Send off a status setting job for each set. + for( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) { + QString imappath = imapPath() + ";UID=" + ( *slit ); + mAccount->setImapStatus(folder(), imappath, flags); + } + } + // FIXME END DUPLICATED FROM KMFOLDERIMAP + + if ( mStatusFlagsJobs ) { + connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ), + this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) ); + return; + } + } + newState( mProgress, i18n("No messages to upload to server")); + serverSyncInternal(); +} + +void KMFolderCachedImap::uploadSeenFlags() +{ + if ( !uidMap.isEmpty() ) { + mStatusFlagsJobs = 0; + newState( mProgress, i18n("Uploading status of messages to server")); + + QValueList<ulong> seenUids, unseenUids; + for( int i = 0; i < count(); ++i ) { + KMMsgBase* msg = getMsgBase( i ); + if( !msg || msg->UID() == 0 ) + // Either not a valid message or not one that is on the server yet + continue; + + if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead ) + seenUids.append( msg->UID() ); + else + unseenUids.append( msg->UID() ); + } + if ( !seenUids.isEmpty() ) { + QStringList sets = KMFolderImap::makeSets( seenUids, true ); + mStatusFlagsJobs += sets.count(); + for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) { + QString imappath = imapPath() + ";UID=" + ( *it ); + mAccount->setImapSeenStatus( folder(), imappath, true ); + } + } + if ( !unseenUids.isEmpty() ) { + QStringList sets = KMFolderImap::makeSets( unseenUids, true ); + mStatusFlagsJobs += sets.count(); + for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) { + QString imappath = imapPath() + ";UID=" + ( *it ); + mAccount->setImapSeenStatus( folder(), imappath, false ); + } + } + + if ( mStatusFlagsJobs ) { + connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ), + this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) ); + return; + } + } + newState( mProgress, i18n("No messages to upload to server")); + serverSyncInternal(); +} + +void KMFolderCachedImap::slotImapStatusChanged(KMFolder* folder, const QString&, bool cont) +{ + if ( mSyncState == SYNC_STATE_INITIAL ){ + //kdDebug(5006) << "IMAP status changed but reset " << endl; + return; // we were reset + } + //kdDebug(5006) << "IMAP status changed for folder: " << folder->prettyURL() << endl; + if ( folder->storage() == this ) { + --mStatusFlagsJobs; + if ( mStatusFlagsJobs == 0 || !cont ) // done or aborting + disconnect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ), + this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) ); + if ( mStatusFlagsJobs == 0 && cont ) { + mProgress += 5; + serverSyncInternal(); + //kdDebug(5006) << "Proceeding with mailcheck." << endl; + } + } +} + +// This is not perfect, what if the status didn't really change? Oh well ... +void KMFolderCachedImap::setStatus( int idx, KMMsgStatus status, bool toggle) +{ + KMFolderMaildir::setStatus( idx, status, toggle ); + mStatusChangedLocally = true; +} + +void KMFolderCachedImap::setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle) +{ + KMFolderMaildir::setStatus(ids, status, toggle); + mStatusChangedLocally = true; +} + +/* Upload new folders to server */ +void KMFolderCachedImap::createNewFolders() +{ + QValueList<KMFolderCachedImap*> newFolders = findNewFolders(); + //kdDebug(5006) << label() << " createNewFolders:" << newFolders.count() << " new folders." << endl; + if( !newFolders.isEmpty() ) { + newState( mProgress, i18n("Creating subfolders on server")); + CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this ); + connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); + connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); + job->start(); + } else { + serverSyncInternal(); + } +} + +QValueList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders() +{ + QValueList<KMFolderCachedImap*> newFolders; + if( folder() && folder()->child() ) { + KMFolderNode *node = folder()->child()->first(); + while( node ) { + if( !node->isDir() ) { + if( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) { + kdError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! " + << node->name() << " is not an IMAP folder\n"; + node = folder()->child()->next(); + assert(0); + } + KMFolderCachedImap* folder = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage()); + if( folder->imapPath().isEmpty() ) { + newFolders << folder; + } + } + node = folder()->child()->next(); + } + } + return newFolders; +} + +bool KMFolderCachedImap::deleteMessages() +{ + /* Delete messages from cache that are gone from the server */ + QPtrList<KMMessage> msgsForDeletion; + + // It is not possible to just go over all indices and remove + // them one by one because the index list can get resized under + // us. So use msg pointers instead + + QStringList uids; + QMap<ulong,int>::const_iterator it = uidMap.constBegin(); + for( ; it != uidMap.end(); it++ ) { + ulong uid ( it.key() ); + if( uid!=0 && !uidsOnServer.find( uid ) ) { + uids << QString::number( uid ); + msgsForDeletion.append( getMsg( *it ) ); + } + } + + if( !msgsForDeletion.isEmpty() ) { +#if MAIL_LOSS_DEBUGGING + if ( KMessageBox::warningYesNo( + 0, i18n( "<qt><p>Mails on the server in folder <b>%1</b> were deleted. " + "Do you want to delete them locally?<br>UIDs: %2</p></qt>" ) + .arg( folder()->prettyURL() ).arg( uids.join(",") ) ) == KMessageBox::Yes ) +#endif + removeMsg( msgsForDeletion ); + } + + if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) ) + return false; + + /* Delete messages from the server that we dont have anymore */ + if( !uidsForDeletionOnServer.isEmpty() ) { + newState( mProgress, i18n("Deleting removed messages from server")); + QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true ); + uidsForDeletionOnServer.clear(); + kdDebug(5006) << "Deleting " << sets.count() << " sets of messages from server folder " << imapPath() << endl; + CachedImapJob *job = new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this ); + connect( job, SIGNAL( result(KMail::FolderJob *) ), + this, SLOT( slotDeleteMessagesResult(KMail::FolderJob *) ) ); + job->start(); + return true; + } else { + + // Nothing to delete on the server, make sure the map is clear again. + // Normally this wouldn't be necessary, but there can be stale maps because of + // https://issues.kolab.org/issue3833. + mDeletedUIDsSinceLastSync.clear(); + return false; + } +} + +void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob* job ) +{ + if ( job->error() ) { + // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages + mSyncState = SYNC_STATE_GET_MESSAGES; + } else { + // deleting on the server went fine, clear the pending deletions cache + mDeletedUIDsSinceLastSync.clear(); + } + mProgress += 10; + serverSyncInternal(); +} + +void KMFolderCachedImap::checkUidValidity() { + // IMAP root folders don't seem to have a UID validity setting. + // Also, don't try the uid validity on new folders + if( imapPath().isEmpty() || imapPath() == "/" ) + // Just proceed + serverSyncInternal(); + else { + newState( mProgress, i18n("Checking folder validity")); + CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this ); + connect( job, SIGNAL(permanentFlags(int)), SLOT(slotPermanentFlags(int)) ); + connect( job, SIGNAL( result( KMail::FolderJob* ) ), + this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) ); + job->start(); + } +} + +void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob* job ) +{ + if ( job->error() ) { // there was an error and the user chose "continue" + // We can't continue doing anything in the same folder though, it would delete all mails. + // But we can continue to subfolders if any. Well we can also try annotation/acl stuff... + mSyncState = SYNC_STATE_HANDLE_INBOX; + } + mProgress += 5; + serverSyncInternal(); +} + +void KMFolderCachedImap::slotPermanentFlags(int flags) +{ + mPermanentFlags = flags; +} + +/* This will only list the messages in a folder. + No directory listing done*/ +void KMFolderCachedImap::listMessages() { + bool groupwareOnly = GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() + && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() + && folder()->isSystemFolder() + && mImapPath == "/INBOX/"; + // Don't list messages on the root folder, and skip the inbox, if this is + // the inbox of a groupware-only dimap account + if( imapPath() == "/" || groupwareOnly ) { + serverSyncInternal(); + return; + } + + if( !mAccount->slave() ) { // sync aborted + resetSyncState(); + emit folderComplete( this, false ); + return; + } + uidsOnServer.clear(); + uidsOnServer.resize( count() * 2 ); + uidsForDeletionOnServer.clear(); + mMsgsForDownload.clear(); + mUidsForDownload.clear(); + // listing is only considered successful if saw a syntactically correct imapdigest + mFoundAnIMAPDigest = false; + + CachedImapJob* job = new CachedImapJob( FolderJob::tListMessages, this ); + connect( job, SIGNAL( result(KMail::FolderJob *) ), + this, SLOT( slotGetLastMessagesResult(KMail::FolderJob *) ) ); + job->start(); +} + +void KMFolderCachedImap::slotGetLastMessagesResult(KMail::FolderJob *job) +{ + getMessagesResult(job, true); +} + +// Connected to the listMessages job in CachedImapJob +void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const QByteArray & data) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + if ( it == mAccount->jobsEnd() ) { // Shouldn't happen + kdDebug(5006) << "could not find job!?!?!" << endl; + // be sure to reset the sync state, if the listing was partial we would + // otherwise delete not-listed mail locally, and on the next sync on the server + // as well + mSyncState = SYNC_STATE_HANDLE_INBOX; + serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */ + return; + } + (*it).cdata += QCString(data, data.size() + 1); + int pos = (*it).cdata.find("\r\n--IMAPDIGEST"); + if (pos > 0) { + int a = (*it).cdata.find("\r\nX-uidValidity:"); + if (a != -1) { + int b = (*it).cdata.find("\r\n", a + 17); + setUidValidity((*it).cdata.mid(a + 17, b - a - 17)); + } + a = (*it).cdata.find("\r\nX-Access:"); + // Only trust X-Access (i.e. the imap select info) if we don't know mUserRights. + // The latter is more accurate (checked on every sync) whereas X-Access is only + // updated when selecting the folder again, which might not happen if using + // RMB / Check Mail in this folder. We don't need two (potentially conflicting) + // sources for the readonly setting, in any case. + if (a != -1 && mUserRights == -1 ) { + int b = (*it).cdata.find("\r\n", a + 12); + const QString access = (*it).cdata.mid(a + 12, b - a - 12); + setReadOnly( access == "Read only" ); + } + (*it).cdata.remove(0, pos); + mFoundAnIMAPDigest = true; + } + pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1); + // Start with something largish when rebuilding the cache + if ( uidsOnServer.size() == 0 ) + uidsOnServer.resize( KMail::nextPrime( 2000 ) ); + const int v = 42; + while (pos >= 0) { + /* + KMMessage msg; + msg.fromString((*it).cdata.mid(16, pos - 16)); + const int flags = msg.headerField("X-Flags").toInt(); + const ulong size = msg.headerField("X-Length").toULong(); + const ulong uid = msg.UID(); + */ + // The below is optimized for speed, not prettiness. The commented out chunk + // above was the solution copied from kmfolderimap, and it's 15-20% slower. + const QCString& entry( (*it).cdata ); + const int indexOfUID = entry.find("X-UID", 16); + const int startOfUIDValue = indexOfUID + 7; + const int indexOfLength = entry.find("X-Length", startOfUIDValue ); // we know length comes after UID + const int startOfLengthValue = indexOfLength + 10; + const int indexOfFlags = entry.find("X-Flags", startOfLengthValue ); // we know flags comes last + const int startOfFlagsValue = indexOfFlags + 9; + + const int flags = entry.mid( startOfFlagsValue, entry.find( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt(); + const ulong size = entry.mid( startOfLengthValue, entry.find( '\r', startOfLengthValue ) - startOfLengthValue ).toULong(); + const ulong uid = entry.mid( startOfUIDValue, entry.find( '\r', startOfUIDValue ) - startOfUIDValue ).toULong(); + + const bool deleted = ( flags & 8 ); + if ( !deleted ) { + if( uid != 0 ) { + if ( uidsOnServer.count() == uidsOnServer.size() ) { + uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) ); + //kdDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl; + } + uidsOnServer.insert( uid, &v ); + } + bool redownload = false; + if ( uid <= lastUid() ) { + /* + * If this message UID is not present locally, then it must + * have been deleted by the user, so we delete it on the + * server also. If we don't have delete permissions on the server, + * re-download the message, it must have vanished by some error, or + * while we still thought we were allowed to delete (ACL change). + * + * This relies heavily on lastUid() being correct at all times. + */ + // kdDebug(5006) << "KMFolderCachedImap::slotGetMessagesData() : folder "<<label()<<" already has msg="<<msg->headerField("Subject") << ", UID="<<uid << ", lastUid = " << mLastUid << endl; + KMMsgBase *existingMessage = findByUID(uid); + if( !existingMessage ) { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should delete it" << endl; +#endif + // double check we deleted it since the last sync + if ( mDeletedUIDsSinceLastSync.contains(uid) ) { + if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::Delete ) ) { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "message with uid " << uid << " is gone from local cache. Must be deleted on server!!!" << endl; +#endif + uidsForDeletionOnServer << uid; + } else { + redownload = true; + } + } else { + kdDebug(5006) << "WARNING: ####### " << endl; + kdDebug(5006) << "Message locally missing but not deleted in folder: " << folder()->prettyURL() << endl; + kdDebug(5006) << "The missing UID: " << uid << ". It will be redownloaded " << endl; + redownload = true; + } + + } else { + // if this is a read only folder, ignore status updates from the server + // since we can't write our status back our local version is what has to + // be considered correct. + if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) { + /* The message is OK, update flags */ + KMFolderImap::flagsToStatus( existingMessage, flags, false, mReadOnly ? INT_MAX : mPermanentFlags ); + } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) { + KMFolderImap::seenFlagToStatus( existingMessage, flags ); + } + } + // kdDebug(5006) << "message with uid " << uid << " found in the local cache. " << endl; + } + if ( uid > lastUid() || redownload ) { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should download it" << endl; +#endif + // The message is new since the last sync, but we might have just uploaded it, in which case + // the uid map already contains it. + if ( !uidMap.contains( uid ) ) { + mMsgsForDownload << KMail::CachedImapJob::MsgForDownload(uid, flags, size); + if( imapPath() == "/INBOX/" ) + mUidsForDownload << uid; + } + // Remember the highest uid and once the download is completed, update mLastUid + if ( uid > mTentativeHighestUid ) { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Setting the tentative highest UID to: " << uid << endl; +#endif + mTentativeHighestUid = uid; + } + } + } + (*it).cdata.remove(0, pos); + (*it).done++; + pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1); + } +} + +void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet ) +{ + mProgress += 10; + if ( !job->error() && !mFoundAnIMAPDigest ) { + kdWarning(5006) << "######## Folderlisting did not complete, but there was no error! " + "Aborting sync of folder: " << folder()->prettyURL() << endl; +#if MAIL_LOSS_DEBUGGING + kmkernel->emergencyExit( i18n("Folder listing failed in interesting ways." ) ); +#endif + } + if( job->error() ) { // error listing messages but the user chose to continue + mContentState = imapNoInformation; + mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder + } else { + if( lastSet ) { // always true here (this comes from online-imap...) + mContentState = imapFinished; + mStatusChangedLocally = false; // we are up to date again + } + } + serverSyncInternal(); +} + +void KMFolderCachedImap::slotProgress(unsigned long done, unsigned long total) +{ + int progressSpan = 100 - 5 - mProgress; + //kdDebug(5006) << "KMFolderCachedImap::slotProgress done=" << done << " total=" << total << "=> mProgress=" << mProgress + ( progressSpan * done ) / total << endl; + // Progress info while retrieving new emails + // (going from mProgress to mProgress+progressSpan) + newState( mProgress + (progressSpan * done) / total, QString::null ); +} + +void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount) +{ + assert( aAccount->isA("KMAcctCachedImap") ); + mAccount = aAccount; + if( imapPath()=="/" ) aAccount->setFolder( folder() ); + + // Folder was renamed in a previous session, and the user didn't sync yet + QString newName = mAccount->renamedFolder( imapPath() ); + if ( !newName.isEmpty() ) + folder()->setLabel( newName ); + + if( !folder() || !folder()->child() || !folder()->child()->count() ) return; + for( KMFolderNode* node = folder()->child()->first(); node; + node = folder()->child()->next() ) + if (!node->isDir()) + static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage())->setAccount(aAccount); +} + +void KMFolderCachedImap::listNamespaces() +{ + ImapAccountBase::ListType type = ImapAccountBase::List; + if ( mAccount->onlySubscribedFolders() ) + type = ImapAccountBase::ListSubscribed; + + kdDebug(5006) << "listNamespaces " << mNamespacesToList << endl; + if ( mNamespacesToList.isEmpty() ) { + mSyncState = SYNC_STATE_DELETE_SUBFOLDERS; + mPersonalNamespacesCheckDone = true; + + QStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS]; + ns += mAccount->namespaces()[ImapAccountBase::SharedNS]; + mNamespacesToCheck = ns.count(); + for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it ) + { + if ( (*it).isEmpty() ) { + // ignore empty listings as they have been listed before + --mNamespacesToCheck; + continue; + } + KMail::ListJob* job = new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) ); + job->setHonorLocalSubscription( true ); + connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&, + const QStringList&, const QStringList&, const ImapAccountBase::jobData&)), + this, SLOT(slotCheckNamespace(const QStringList&, const QStringList&, + const QStringList&, const QStringList&, const ImapAccountBase::jobData&))); + job->start(); + } + if ( mNamespacesToCheck == 0 ) { + serverSyncInternal(); + } + return; + } + mPersonalNamespacesCheckDone = false; + + QString ns = mNamespacesToList.front(); + mNamespacesToList.pop_front(); + + mSyncState = SYNC_STATE_LIST_SUBFOLDERS2; + newState( mProgress, i18n("Retrieving folders for namespace %1").arg(ns)); + KMail::ListJob* job = new KMail::ListJob( mAccount, type, this, + mAccount->addPathToNamespace( ns ) ); + job->setNamespace( ns ); + job->setHonorLocalSubscription( true ); + connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&, + const QStringList&, const QStringList&, const ImapAccountBase::jobData&)), + this, SLOT(slotListResult(const QStringList&, const QStringList&, + const QStringList&, const QStringList&, const ImapAccountBase::jobData&))); + job->start(); +} + +void KMFolderCachedImap::slotCheckNamespace( const QStringList& subfolderNames, + const QStringList& subfolderPaths, + const QStringList& subfolderMimeTypes, + const QStringList& subfolderAttributes, + const ImapAccountBase::jobData& jobData ) +{ + Q_UNUSED( subfolderPaths ); + Q_UNUSED( subfolderMimeTypes ); + Q_UNUSED( subfolderAttributes ); + --mNamespacesToCheck; + kdDebug(5006) << "slotCheckNamespace " << subfolderNames << ",remain=" << + mNamespacesToCheck << endl; + + // get a correct foldername: + // strip / and make sure it does not contain the delimiter + QString name = jobData.path.mid( 1, jobData.path.length()-2 ); + name.remove( mAccount->delimiterForNamespace( name ) ); + if ( name.isEmpty() ) { + // should not happen + kdWarning(5006) << "slotCheckNamespace: ignoring empty folder!" << endl; + return; + } + + folder()->createChildFolder(); + KMFolderNode *node = 0; + for ( node = folder()->child()->first(); node; + node = folder()->child()->next()) + { + if ( !node->isDir() && node->name() == name ) + break; + } + if ( !subfolderNames.isEmpty() ) { + if ( node ) { + // folder exists so we have nothing to do - it will be listed later + kdDebug(5006) << "found namespace folder " << name << endl; + } else + { + // create folder + kdDebug(5006) << "create namespace folder " << name << endl; + KMFolder* newFolder = folder()->child()->createFolder( name, false, + KMFolderTypeCachedImap ); + if ( newFolder ) { + KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>( newFolder->storage() ); + f->setImapPath( mAccount->addPathToNamespace( name ) ); + f->setNoContent( true ); + f->setAccount( mAccount ); + f->close("cachedimap"); + kmkernel->dimapFolderMgr()->contentsChanged(); + } + } + } else { + if ( node ) { + kdDebug(5006) << "delete namespace folder " << name << endl; + KMFolder* fld = static_cast<KMFolder*>(node); + kmkernel->dimapFolderMgr()->remove( fld ); + } + } + + if ( mNamespacesToCheck == 0 ) { + // all namespaces are done so continue with the next step + serverSyncInternal(); + } +} + +// This lists the subfolders on the server +// and (in slotListResult) takes care of folders that have been removed on the server +bool KMFolderCachedImap::listDirectory() +{ + if( !mAccount->slave() ) { // sync aborted + resetSyncState(); + emit folderComplete( this, false ); + return false; + } + mSubfolderState = imapInProgress; + + // get the folders + ImapAccountBase::ListType type = ImapAccountBase::List; + if ( mAccount->onlySubscribedFolders() ) + type = ImapAccountBase::ListSubscribed; + KMail::ListJob* job = new KMail::ListJob( mAccount, type, this ); + job->setHonorLocalSubscription( true ); + connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&, + const QStringList&, const QStringList&, const ImapAccountBase::jobData&)), + this, SLOT(slotListResult(const QStringList&, const QStringList&, + const QStringList&, const QStringList&, const ImapAccountBase::jobData&))); + job->start(); + + return true; +} + +void KMFolderCachedImap::slotListResult( const QStringList& folderNames, + const QStringList& folderPaths, + const QStringList& folderMimeTypes, + const QStringList& folderAttributes, + const ImapAccountBase::jobData& jobData ) +{ + Q_UNUSED( jobData ); + //kdDebug(5006) << label() << ": folderNames=" << folderNames << " folderPaths=" + //<< folderPaths << " mimeTypes=" << folderMimeTypes << endl; + mSubfolderNames = folderNames; + mSubfolderPaths = folderPaths; + mSubfolderMimeTypes = folderMimeTypes; + mSubfolderState = imapFinished; + mSubfolderAttributes = folderAttributes; + kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl; + + folder()->createChildFolder(); + KMFolderNode *node = folder()->child()->first(); + bool root = ( this == mAccount->rootFolder() ); + + QPtrList<KMFolder> toRemove; + bool emptyList = ( root && mSubfolderNames.empty() ); + if ( !emptyList ) { + while (node) { + if (!node->isDir() ) { + KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage()); + + if ( mSubfolderNames.findIndex(node->name()) == -1 ) { + QString name = node->name(); + // as more than one namespace can be listed in the root folder we need to make sure + // that the folder is within the current namespace + bool isInNamespace = ( jobData.curNamespace.isEmpty() || + jobData.curNamespace == mAccount->namespaceForFolder( f ) ); + // ignore some cases + bool ignore = root && ( f->imapPath() == "/INBOX/" || + mAccount->isNamespaceFolder( name ) || !isInNamespace ); + + // This subfolder isn't present on the server + if( !f->imapPath().isEmpty() && !ignore ) { + // The folder has an imap path set, so it has been + // on the server before. Delete it locally. + toRemove.append( f->folder() ); + kdDebug(5006) << node->name() << " isn't on the server. It has an imapPath -> delete it locally" << endl; + } + } else { // folder both local and on server + //kdDebug(5006) << node->name() << " is on the server." << endl; + + /** + * Store the folder attributes for every subfolder. + */ + int index = mSubfolderNames.findIndex( node->name() ); + f->mFolderAttributes = folderAttributes[ index ]; + } + } else { + //kdDebug(5006) << "skipping dir node:" << node->name() << endl; + } + node = folder()->child()->next(); + } + } + + for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() ) { + rescueUnsyncedMessagesAndDeleteFolder( doomed ); + } + + mProgress += 5; + + // just in case there is nothing to rescue + slotRescueDone( 0 ); +} + +// This synchronizes the local folders as needed (creation/deletion). No network communication here. +void KMFolderCachedImap::listDirectory2() +{ + QString path = folder()->path(); + kmkernel->dimapFolderMgr()->quiet(true); + + bool root = ( this == mAccount->rootFolder() ); + if ( root && !mAccount->hasInbox() ) + { + KMFolderCachedImap *f = 0; + KMFolderNode *node; + // create the INBOX + for (node = folder()->child()->first(); node; node = folder()->child()->next()) + if (!node->isDir() && node->name() == "INBOX") break; + if (node) { + f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage()); + } else { + KMFolder* newFolder = folder()->child()->createFolder("INBOX", true, KMFolderTypeCachedImap); + if ( newFolder ) { + f = static_cast<KMFolderCachedImap*>(newFolder->storage()); + } + } + if ( f ) { + f->setAccount( mAccount ); + f->setImapPath( "/INBOX/" ); + f->folder()->setLabel( i18n("inbox") ); + } + if (!node) { + if ( f ) + f->close("cachedimap"); + kmkernel->dimapFolderMgr()->contentsChanged(); + } + // so we have an INBOX + mAccount->setHasInbox( true ); + } + + if ( root && !mSubfolderNames.isEmpty() ) { + KMFolderCachedImap* parent = + findParent( mSubfolderPaths.first(), mSubfolderNames.first() ); + if ( parent ) { + kdDebug(5006) << "KMFolderCachedImap::listDirectory2 - pass listing to " + << parent->label() << endl; + mSubfolderNames.clear(); + } + } + + // Find all subfolders present on server but not on disk + QValueVector<int> foldersNewOnServer; + for (uint i = 0; i < mSubfolderNames.count(); i++) { + + // Find the subdir, if already present + KMFolderCachedImap *f = 0; + KMFolderNode *node = 0; + for (node = folder()->child()->first(); node; + node = folder()->child()->next()) + if (!node->isDir() && node->name() == mSubfolderNames[i]) break; + + if (!node) { + // This folder is not present here + // Either it's new on the server, or we just deleted it. + QString subfolderPath = mSubfolderPaths[i]; + // The code used to look at the uidcache to know if it was "just deleted". + // But this breaks with noContent folders and with shared folders. + // So instead we keep a list in the account. + bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath ); + // That list is saved/restored across sessions, but to avoid any mistake, + // ask for confirmation if the folder was deleted in a previous session + // (could be that the folder was deleted & recreated meanwhile from another client...) + if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) { + locallyDeleted = KMessageBox::warningYesNo( + 0, i18n( "<qt><p>It seems that the folder <b>%1</b> was deleted. Do you want to delete it from the server?</p></qt>" ).arg( mSubfolderNames[i] ), QString::null, KStdGuiItem::del(), KStdGuiItem::cancel() ) == KMessageBox::Yes; + } + + if ( locallyDeleted ) { + kdDebug(5006) << subfolderPath << " was deleted locally => delete on server." << endl; + foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath ); // grab all subsubfolders too + } else { + kdDebug(5006) << subfolderPath << " is a new folder on the server => create local cache" << endl; + foldersNewOnServer.append( i ); + } + } else { // Folder found locally + if( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap ) + f = dynamic_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage()); + if( f ) { + // kdDebug(5006) << "folder("<<f->name()<<")->imapPath()=" << f->imapPath() + // << "\nSetting imapPath " << mSubfolderPaths[i] << endl; + // Write folder settings + f->setAccount(mAccount); + f->setNoContent(mSubfolderMimeTypes[i] == "inode/directory"); + f->setNoChildren(mSubfolderMimeTypes[i] == "message/digest"); + f->setImapPath(mSubfolderPaths[i]); + } + } + } + + /* In case we are ignoring non-groupware folders, and this is the groupware + * main account, find out the contents types of folders that have newly + * appeared on the server. Otherwise just create them and finish listing. + * If a folder is already known to be locally unsubscribed, it won't be + * listed at all, on this level, so these are only folders that we are + * seeing for the first time. */ + + /* Note: We ask the globalsettings, and not the current state of the + * kmkernel->iCalIface().isEnabled(), since that is false during the + * very first sync, where we already want to filter. */ + if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() + && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() + && mAccount->hasAnnotationSupport() + && GlobalSettings::self()->theIMAPResourceEnabled() + && !foldersNewOnServer.isEmpty() ) { + + QStringList paths; + for ( uint i = 0; i < foldersNewOnServer.count(); ++i ) + paths << mSubfolderPaths[ foldersNewOnServer[i] ]; + + AnnotationJobs::MultiUrlGetAnnotationJob* job = + AnnotationJobs::multiUrlGetAnnotation( mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE ); + ImapAccountBase::jobData jd( QString::null, folder() ); + jd.cancellable = true; + mAccount->insertJob(job, jd); + connect( job, SIGNAL(result(KIO::Job *)), + SLOT(slotMultiUrlGetAnnotationResult(KIO::Job *)) ); + + } else { + createFoldersNewOnServerAndFinishListing( foldersNewOnServer ); + } +} + +void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const QValueVector<int> foldersNewOnServer ) +{ + for ( uint i = 0; i < foldersNewOnServer.count(); ++i ) { + int idx = foldersNewOnServer[i]; + KMFolder* newFolder = folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap); + if (newFolder) { + KMFolderCachedImap *f = dynamic_cast<KMFolderCachedImap*>(newFolder->storage()); + kdDebug(5006) << " ####### Locally creating folder " << mSubfolderNames[idx] <<endl; + f->close("cachedimap"); + f->setAccount(mAccount); + f->mAnnotationFolderType = "FROMSERVER"; + f->setNoContent(mSubfolderMimeTypes[idx] == "inode/directory"); + f->setNoChildren(mSubfolderMimeTypes[idx] == "message/digest"); + f->setImapPath(mSubfolderPaths[idx]); + f->mFolderAttributes = mSubfolderAttributes[idx]; + kdDebug(5006) << " ####### Attributes: " << f->mFolderAttributes <<endl; + //kdDebug(5006) << subfolderPath << ": mAnnotationFolderType set to FROMSERVER" << endl; + kmkernel->dimapFolderMgr()->contentsChanged(); + } else { + kdDebug(5006) << "can't create folder " << mSubfolderNames[idx] <<endl; + } + } + + kmkernel->dimapFolderMgr()->quiet(false); + emit listComplete(this); + if ( !mPersonalNamespacesCheckDone ) { + // we're not done with the namespaces + mSyncState = SYNC_STATE_LIST_NAMESPACES; + } + serverSyncInternal(); +} + +//----------------------------------------------------------------------------- +KMFolderCachedImap* KMFolderCachedImap::findParent( const QString& path, + const QString& name ) +{ + QString parent = path.left( path.length() - name.length() - 2 ); + if ( parent.length() > 1 ) + { + // extract name of the parent + parent = parent.right( parent.length() - 1 ); + if ( parent != label() ) + { + KMFolderNode *node = folder()->child()->first(); + // look for a better parent + while ( node ) + { + if ( node->name() == parent ) + { + KMFolder* fld = static_cast<KMFolder*>(node); + KMFolderCachedImap* imapFld = + static_cast<KMFolderCachedImap*>( fld->storage() ); + return imapFld; + } + node = folder()->child()->next(); + } + } + } + return 0; +} + +void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool success) +{ + Q_UNUSED(sub); + //kdDebug(5006) << label() << " slotSubFolderComplete: " << sub->label() << endl; + if ( success ) { + serverSyncInternal(); + } + else + { + // success == false means the sync was aborted. + if ( mCurrentSubfolder ) { + Q_ASSERT( sub == mCurrentSubfolder ); + disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), + this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); + mCurrentSubfolder = 0; + } + + mSubfoldersForSync.clear(); + mSyncState = SYNC_STATE_INITIAL; + close("cachedimap"); + emit folderComplete( this, false ); + } +} + +void KMFolderCachedImap::slotSimpleData(KIO::Job * job, const QByteArray & data) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + if (it == mAccount->jobsEnd()) return; + QBuffer buff((*it).data); + buff.open(IO_WriteOnly | IO_Append); + buff.writeBlock(data.data(), data.size()); + buff.close(); +} + +FolderJob* +KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder, + QString, const AttachmentStrategy* ) const +{ + QPtrList<KMMessage> msgList; + msgList.append( msg ); + CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 ); + job->setParentFolder( this ); + return job; +} + +FolderJob* +KMFolderCachedImap::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets, + FolderJob::JobType jt, KMFolder *folder ) const +{ + //FIXME: how to handle sets here? + Q_UNUSED( sets ); + CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 ); + job->setParentFolder( this ); + return job; +} + +void +KMFolderCachedImap::setUserRights( unsigned int userRights ) +{ + mUserRights = userRights; + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); +} + +void +KMFolderCachedImap::slotReceivedUserRights( KMFolder* folder ) +{ + if ( folder->storage() == this ) { + disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ), + this, SLOT( slotReceivedUserRights( KMFolder* ) ) ); + if ( mUserRights == 0 ) // didn't work + mUserRights = -1; // error code (used in folderdia) + else + setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 ); + mProgress += 5; + serverSyncInternal(); + } +} + +void +KMFolderCachedImap::setReadOnly( bool readOnly ) +{ + if ( readOnly != mReadOnly ) { + mReadOnly = readOnly; + emit readOnlyChanged( folder() ); + } +} + +void +KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job*, const KMail::ACLList& aclList ) +{ + if ( folder->storage() == this ) { + disconnect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), + this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); + mACLList = aclList; + serverSyncInternal(); + } +} + +void +KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo& info ) +{ + setQuotaInfo( info ); +} + +void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info ) +{ + if ( info != mQuotaInfo ) { + mQuotaInfo = info; + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); + emit folderSizeChanged(); + } +} + +void +KMFolderCachedImap::setACLList( const ACLList& arr ) +{ + mACLList = arr; +} + +void +KMFolderCachedImap::slotMultiSetACLResult(KIO::Job *job) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen + if ( (*it).parent != folder() ) return; // Shouldn't happen + + if ( job->error() ) + // Display error but don't abort the sync just for this + // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel + job->showErrorDialog(); + else + kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL ); + + if (mAccount->slave()) mAccount->removeJob(job); + serverSyncInternal(); +} + +void +KMFolderCachedImap::slotACLChanged( const QString& userId, int permissions ) +{ + // The job indicates success in changing the permissions for this user + // -> we note that it's been done. + for( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) { + if ( (*it).userId == userId && (*it).permissions == permissions ) { + if ( permissions == -1 ) // deleted + mACLList.erase( it ); + else // added/modified + (*it).changed = false; + return; + } + } +} + +// called by KMAcctCachedImap::killAllJobs +void KMFolderCachedImap::resetSyncState() +{ + if ( mSyncState == SYNC_STATE_INITIAL ) return; + mSubfoldersForSync.clear(); + mSyncState = SYNC_STATE_INITIAL; + close("cachedimap"); + // Don't use newState here, it would revert to mProgress (which is < current value when listing messages) + KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem(); + QString str = i18n("Aborted"); + if (progressItem) + progressItem->setStatus( str ); + emit statusMsg( str ); +} + +void KMFolderCachedImap::slotIncreaseProgress() +{ + mProgress += 5; +} + +void KMFolderCachedImap::newState( int progress, const QString& syncStatus ) +{ + //kdDebug() << k_funcinfo << folder() << " " << mProgress << " " << syncStatus << endl; + KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem(); + if( progressItem ) + progressItem->setCompletedItems( progress ); + if ( !syncStatus.isEmpty() ) { + QString str; + // For a subfolder, show the label. But for the main folder, it's already shown. + if ( mAccount->imapFolder() == this ) + str = syncStatus; + else + str = QString( "%1: %2" ).arg( label() ).arg( syncStatus ); + if( progressItem ) + progressItem->setStatus( str ); + emit statusMsg( str ); + } + if( progressItem ) + progressItem->updateProgress(); +} + +void KMFolderCachedImap::setSubfolderState( imapState state ) +{ + mSubfolderState = state; + if ( state == imapNoInformation && folder()->child() ) + { + // pass through to childs + KMFolderNode* node; + QPtrListIterator<KMFolderNode> it( *folder()->child() ); + for ( ; (node = it.current()); ) + { + ++it; + if (node->isDir()) continue; + KMFolder *folder = static_cast<KMFolder*>(node); + static_cast<KMFolderCachedImap*>(folder->storage())->setSubfolderState( state ); + } + } +} + +void KMFolderCachedImap::setImapPath(const QString &path) +{ + mImapPath = path; +} + +// mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc) +// It is updated from the folder contents type and whether it's a standard resource folder. +// This happens during the syncing phase and during initFolder for a new folder. +// Don't do it earlier, e.g. from setContentsType: +// on startup, it's too early there to know if this is a standard resource folder. +void KMFolderCachedImap::updateAnnotationFolderType() +{ + QString oldType = mAnnotationFolderType; + QString oldSubType; + int dot = oldType.find( '.' ); + if ( dot != -1 ) { + oldType.truncate( dot ); + oldSubType = mAnnotationFolderType.mid( dot + 1 ); + } + + QString newType, newSubType; + // We want to store an annotation on the folder only if using the kolab storage. + if ( kmkernel->iCalIface().storageFormat( folder() ) == KMailICalIfaceImpl::StorageXML ) { + newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType ); + if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) ) + newSubType = "default"; + else + newSubType = oldSubType; // preserve unknown subtypes, like drafts etc. And preserve ".default" too. + } + + //kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: " << newType << " " << newSubType << endl; + if ( newType != oldType || newSubType != oldSubType ) { + mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString::null : "."+newSubType ); + mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync + kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl; + } + // Ensure that further readConfig()s don't lose mAnnotationFolderType + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); +} + +void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor ) +{ + if ( mIncidencesFor != incfor ) { + mIncidencesFor = incfor; + mIncidencesForChanged = true; + } +} + +void KMFolderCachedImap::slotAnnotationResult(const QString& entry, const QString& value, bool found) +{ + if ( entry == KOLAB_FOLDERTYPE ) { + // There are four cases. + // 1) no content-type on server -> set it + // 2) different content-type on server, locally changed -> set it (we don't even come here) + // 3) different (known) content-type on server, no local change -> get it + // 4) different unknown content-type on server, probably some older version -> set it + if ( found ) { + QString type = value; + QString subtype; + int dot = value.find( '.' ); + if ( dot != -1 ) { + type.truncate( dot ); + subtype = value.mid( dot + 1 ); + } + bool foundKnownType = false; + for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) { + FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i ); + if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) { + // Case 3: known content-type on server, get it + //kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: found known type of annotation" << endl; + if ( contentsType != ContentsTypeMail ) + kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML ); + mAnnotationFolderType = value; + if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent() + && GlobalSettings::self()->theIMAPResourceEnabled() + && subtype == "default" ) { + // Truncate subtype if this folder can't be a default resource folder for us, + // although it apparently is for someone else. + mAnnotationFolderType = type; + kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: parent folder is " << folder()->parent()->owner()->idString() << " => truncating annotation to " << value << endl; + } + setContentsType( contentsType ); + mAnnotationFolderTypeChanged = false; // we changed it, not the user + foundKnownType = true; + + // Users don't read events/contacts/etc. in kmail, so mark them all as read. + // This is done in cachedimapjob when getting new messages, but do it here too, + // for the initial set of messages when we didn't know this was a resource folder yet, + // for old folders, etc. + if ( contentsType != ContentsTypeMail ) + markUnreadAsRead(); + + // Ensure that further readConfig()s don't lose mAnnotationFolderType + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); + break; + } + } + if ( !foundKnownType && !mReadOnly ) { + //kdDebug(5006) << "slotGetAnnotationResult: no known type of annotation found, will need to set it" << endl; + // Case 4: server has strange content-type, set it to what we need + mAnnotationFolderTypeChanged = true; + } + // TODO handle subtype (inbox, drafts, sentitems, junkemail) + } + else if ( !mReadOnly ) { + // Case 1: server doesn't have content-type, set it + //kdDebug(5006) << "slotGetAnnotationResult: no annotation found, will need to set it" << endl; + mAnnotationFolderTypeChanged = true; + } + } else if ( entry == KOLAB_INCIDENCESFOR ) { + if ( found ) { + mIncidencesFor = incidencesForFromString( value ); + Q_ASSERT( mIncidencesForChanged == false ); + } + } +} + +void KMFolderCachedImap::slotGetAnnotationResult( KIO::Job* job ) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + Q_ASSERT( it != mAccount->jobsEnd() ); + if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen + Q_ASSERT( (*it).parent == folder() ); + if ( (*it).parent != folder() ) return; // Shouldn't happen + + AnnotationJobs::GetAnnotationJob* annjob = static_cast<AnnotationJobs::GetAnnotationJob *>( job ); + if ( annjob->error() ) { + if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) { + // that's when the imap server doesn't support annotations + if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML + && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) + KMessageBox::error( 0, i18n( "The IMAP server %1 does not have support for IMAP annotations. The XML storage cannot be used on this server; please re-configure KMail differently." ).arg( mAccount->host() ) ); + mAccount->setHasNoAnnotationSupport(); + } + else + kdWarning(5006) << "slotGetAnnotationResult: " << job->errorString() << endl; + } + + if (mAccount->slave()) mAccount->removeJob(job); + mProgress += 2; + serverSyncInternal(); +} + +void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KIO::Job* job ) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + Q_ASSERT( it != mAccount->jobsEnd() ); + if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen + Q_ASSERT( (*it).parent == folder() ); + if ( (*it).parent != folder() ) return; // Shouldn't happen + + QValueVector<int> folders; + AnnotationJobs::MultiUrlGetAnnotationJob* annjob + = static_cast<AnnotationJobs::MultiUrlGetAnnotationJob *>( job ); + if ( annjob->error() ) { + if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) { + // that's when the imap server doesn't support annotations + if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML + && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) + KMessageBox::error( 0, i18n( "The IMAP server %1 doesn't have support for imap annotations. The XML storage cannot be used on this server, please re-configure KMail differently" ).arg( mAccount->host() ) ); + mAccount->setHasNoAnnotationSupport(); + } + else + kdWarning(5006) << "slotGetMultiUrlAnnotationResult: " << job->errorString() << endl; + } else { + // we got the annotation allright, let's filter out the ones with the wrong type + QMap<QString, QString> annotations = annjob->annotations(); + QMap<QString, QString>::Iterator it = annotations.begin(); + for ( ; it != annotations.end(); ++it ) { + const QString folderPath = it.key(); + const QString annotation = it.data(); + kdDebug(5006) << k_funcinfo << "Folder: " << folderPath << " has type: " << annotation << endl; + // we're only interested in the main type + QString type(annotation); + int dot = annotation.find( '.' ); + if ( dot != -1 ) type.truncate( dot ); + type = type.simplifyWhiteSpace(); + + const int idx = mSubfolderPaths.findIndex( folderPath ); + const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory"; + if ( ( isNoContent && type.isEmpty() ) + || ( !type.isEmpty() && type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) { + folders.append( idx ); + kdDebug(5006) << k_funcinfo << " subscribing to: " << folderPath << endl; + } else { + kdDebug(5006) << k_funcinfo << " automatically unsubscribing from: " << folderPath << endl; + mAccount->changeLocalSubscription( folderPath, false ); + } + } + } + + if (mAccount->slave()) mAccount->removeJob(job); + createFoldersNewOnServerAndFinishListing( folders ); +} + +void KMFolderCachedImap::slotQuotaResult( KIO::Job* job ) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + Q_ASSERT( it != mAccount->jobsEnd() ); + if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen + Q_ASSERT( (*it).parent == folder() ); + if ( (*it).parent != folder() ) return; // Shouldn't happen + + QuotaJobs::GetStorageQuotaJob* quotajob = static_cast<QuotaJobs::GetStorageQuotaJob *>( job ); + QuotaInfo empty; + if ( quotajob->error() ) { + if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) { + // that's when the imap server doesn't support quota + mAccount->setHasNoQuotaSupport(); + setQuotaInfo( empty ); + } + else + kdWarning(5006) << "slotGetQuotaResult: " << job->errorString() << endl; + } + + if (mAccount->slave()) mAccount->removeJob(job); + mProgress += 2; + serverSyncInternal(); +} + +void +KMFolderCachedImap::slotAnnotationChanged( const QString& entry, const QString& attribute, const QString& value ) +{ + Q_UNUSED( attribute ); + Q_UNUSED( value ); + //kdDebug(5006) << k_funcinfo << entry << " " << attribute << " " << value << endl; + if ( entry == KOLAB_FOLDERTYPE ) + mAnnotationFolderTypeChanged = false; + else if ( entry == KOLAB_INCIDENCESFOR ) { + mIncidencesForChanged = false; + // The incidences-for changed, we must trigger the freebusy creation. + // HACK: in theory we would need a new enum value for this. + kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL ); + } +} + +void KMFolderCachedImap::slotTestAnnotationResult(KIO::Job *job) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + Q_ASSERT( it != mAccount->jobsEnd() ); + if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen + Q_ASSERT( (*it).parent == folder() ); + if ( (*it).parent != folder() ) return; // Shouldn't happen + + mAccount->setAnnotationCheckPassed( true ); + if ( job->error() ) { + kdDebug(5006) << "Test Annotation was not passed, disabling annotation support" << endl; + mAccount->setHasNoAnnotationSupport( ); + } else { + kdDebug(5006) << "Test Annotation was passed OK" << endl; + } + if (mAccount->slave()) mAccount->removeJob(job); + serverSyncInternal(); +} + +void +KMFolderCachedImap::slotSetAnnotationResult(KIO::Job *job) +{ + KMAcctCachedImap::JobIterator it = mAccount->findJob(job); + if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen + if ( (*it).parent != folder() ) return; // Shouldn't happen + + bool cont = true; + if ( job->error() ) { + // Don't show error if the server doesn't support ANNOTATEMORE and this folder only contains mail + if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION && contentsType() == ContentsTypeMail ) { + if (mAccount->slave()) mAccount->removeJob(job); + } else { + cont = mAccount->handleJobError( job, i18n( "Error while setting annotation: " ) + '\n' ); + } + } else { + if (mAccount->slave()) mAccount->removeJob(job); + } + if ( cont ) + serverSyncInternal(); +} + +void KMFolderCachedImap::slotUpdateLastUid() +{ + if( mTentativeHighestUid != 0 ) { + + // Sanity checking: + // By now all new mails should be downloaded, which means + // that iteration over the folder should yield only UIDs + // lower or equal to what we think the highes ist, and the + // highest one as well. If not, our notion of the highest + // uid we've seen thus far is wrong, which is dangerous, so + // don't update the mLastUid, then. + // Not entirely true though, mails might have been moved out + // of the folder already by filters, thus giving us a higher tentative + // uid than we actually observe here. + bool sane = count() == 0; + + for (int i=0;i<count(); i++ ) { + ulong uid = getMsgBase(i)->UID(); + if ( uid > mTentativeHighestUid && uid > lastUid() ) { + kdWarning(5006) << "DANGER: Either the server listed a wrong highest uid, " + "or we parsed it wrong. Send email to adam@kde.org, please, and include this log." << endl; + kdWarning(5006) << "uid: " << uid << " mTentativeHighestUid: " << mTentativeHighestUid << endl; + assert( false ); + break; + } else { + sane = true; + } + } + if (sane) { +#if MAIL_LOSS_DEBUGGING + kdDebug(5006) << "Tentative highest UID test was sane, writing out: " << mTentativeHighestUid << endl; +#endif + setLastUid( mTentativeHighestUid ); + } + } + mTentativeHighestUid = 0; +} + +bool KMFolderCachedImap::isMoveable() const +{ + return ( hasChildren() == HasNoChildren && + !folder()->isSystemFolder() ) ? true : false; +} + +void KMFolderCachedImap::slotFolderDeletionOnServerFinished() +{ + for ( QStringList::const_iterator it = foldersForDeletionOnServer.constBegin(); + it != foldersForDeletionOnServer.constEnd(); ++it ) { + KURL url( mAccount->getUrl() ); + url.setPath( *it ); + kmkernel->iCalIface().folderDeletedOnServer( url ); + } + serverSyncInternal(); +} + +int KMFolderCachedImap::createIndexFromContentsRecursive() +{ + if ( !folder() || !folder()->child() ) + return 0; + + KMFolderNode *node = 0; + for( QPtrListIterator<KMFolderNode> it( *folder()->child() ); (node = it.current()); ++it ) { + if( !node->isDir() ) { + KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage()); + kdDebug() << k_funcinfo << "Re-indexing: " << storage->folder()->label() << endl; + int rv = storage->createIndexFromContentsRecursive(); + if ( rv > 0 ) + return rv; + } + } + + return createIndexFromContents(); +} + +void KMFolderCachedImap::setAlarmsBlocked( bool blocked ) +{ + mAlarmsBlocked = blocked; +} + +bool KMFolderCachedImap::alarmsBlocked() const +{ + return mAlarmsBlocked; +} + +bool KMFolderCachedImap::isCloseToQuota() const +{ + bool closeToQuota = false; + if ( mQuotaInfo.isValid() && mQuotaInfo.max().toInt() > 0 ) { + const int ratio = mQuotaInfo.current().toInt() * 100 / mQuotaInfo.max().toInt(); + //kdDebug(5006) << "Quota ratio: " << ratio << "% " << mQuotaInfo.toString() << endl; + closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() ); + } + //kdDebug(5006) << "Folder: " << folder()->prettyURL() << " is over quota: " << closeToQuota << endl; + return closeToQuota; +} + +KMCommand* KMFolderCachedImap::rescueUnsyncedMessages() +{ + QValueList<unsigned long> newMsgs = findNewMessages(); + kdDebug() << k_funcinfo << newMsgs << " of " << count() << endl; + if ( newMsgs.isEmpty() ) + return 0; + KMFolder *dest = 0; + bool manualMove = true; + while ( GlobalSettings::autoLostFoundMove() ) { + // find the inbox of this account + KMFolder *inboxFolder = kmkernel->findFolderById( QString(".%1.directory/INBOX").arg( account()->id() ) ); + if ( !inboxFolder ) { + kdWarning(5006) << k_funcinfo << "inbox not found!" << endl; + break; + } + KMFolderDir *inboxDir = inboxFolder->child(); + if ( !inboxDir && !inboxFolder->storage() ) + break; + assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap ); + + // create lost+found folder if needed + KMFolderNode *node; + KMFolder *lfFolder = 0; + if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) { + kdDebug(5006) << k_funcinfo << "creating lost+found folder" << endl; + KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder( + i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir ); + if ( !folder || !folder->storage() ) + break; + static_cast<KMFolderCachedImap*>( folder->storage() )->initializeFrom( + static_cast<KMFolderCachedImap*>( inboxFolder->storage() ) ); + folder->storage()->setContentsType( KMail::ContentsTypeMail ); + folder->storage()->writeConfig(); + lfFolder = folder; + } else { + kdDebug(5006) << k_funcinfo << "found lost+found folder" << endl; + lfFolder = dynamic_cast<KMFolder*>( node ); + } + if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() ) + break; + + // create subfolder for this incident + QDate today = QDate::currentDate(); + QString baseName = folder()->label() + "-" + QString::number( today.year() ) + + (today.month() < 10 ? "0" : "" ) + QString::number( today.month() ) + + (today.day() < 10 ? "0" : "" ) + QString::number( today.day() ); + QString name = baseName; + int suffix = 0; + while ( (node = lfFolder->child()->hasNamedFolder( name )) ) { + ++suffix; + name = baseName + '-' + QString::number( suffix ); + } + kdDebug(5006) << k_funcinfo << "creating lost+found folder " << name << endl; + dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() ); + if ( !dest || !dest->storage() ) + break; + static_cast<KMFolderCachedImap*>( dest->storage() )->initializeFrom( + static_cast<KMFolderCachedImap*>( lfFolder->storage() ) ); + dest->storage()->setContentsType( contentsType() ); + dest->storage()->writeConfig(); + + KMessageBox::sorry( 0, i18n("<p>There are new messages in folder <b>%1</b>, which " + "have not been uploaded to the server yet, but the folder has been deleted " + "on the server or you do not " + "have sufficient access rights on the folder to upload them.</p>" + "<p>All affected messages will therefore be moved to <b>%2</b> " + "to avoid data loss.</p>").arg( folder()->prettyURL() ).arg( dest->prettyURL() ), + i18n("Insufficient access rights") ); + manualMove = false; + break; + } + + if ( manualMove ) { + const QString msg ( i18n( "<p>There are new messages in this folder (%1), which " + "have not been uploaded to the server yet, but the folder has been deleted " + "on the server or you do not " + "have sufficient access rights on the folder now to upload them. " + "Please contact your administrator to allow upload of new messages " + "to you, or move them out of this folder.</p> " + "<p>Do you want to move these messages to another folder now?</p>").arg( folder()->prettyURL() ) ); + if ( KMessageBox::warningYesNo( 0, msg, QString::null, i18n("Move"), i18n("Do Not Move") ) == KMessageBox::Yes ) { + KMail::KMFolderSelDlg dlg( kmkernel->getKMMainWidget(), + i18n("Move Messages to Folder"), true ); + if ( dlg.exec() ) { + dest = dlg.folder(); + } + } + } + if ( dest ) { + QPtrList<KMMsgBase> msgs; + for( int i = 0; i < count(); ++i ) { + KMMsgBase *msg = getMsgBase( i ); + if( !msg ) continue; /* what goes on if getMsg() returns 0? */ + if ( msg->UID() == 0 ) + msgs.append( msg ); + } + KMCommand *command = new KMMoveCommand( dest, msgs ); + command->start(); + return command; + } + return 0; +} + +void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root ) +{ + kdDebug() << k_funcinfo << folder << " " << root << endl; + if ( root ) + mToBeDeletedAfterRescue.append( folder ); + folder->open("cachedimap"); + KMFolderCachedImap* storage = dynamic_cast<KMFolderCachedImap*>( folder->storage() ); + if ( storage ) { + KMCommand *command = storage->rescueUnsyncedMessages(); + if ( command ) { + connect( command, SIGNAL(completed(KMCommand*)), + SLOT(slotRescueDone(KMCommand*)) ); + ++mRescueCommandCount; + } else { + // nothing to rescue, close folder + // (we don't need to close it in the other case, it will be deleted anyway) + folder->close("cachedimap"); + } + } + if ( folder->child() ) { + KMFolderNode *node = folder->child()->first(); + while (node) { + if (!node->isDir() ) { + KMFolder *subFolder = static_cast<KMFolder*>( node ); + rescueUnsyncedMessagesAndDeleteFolder( subFolder, false ); + } + node = folder->child()->next(); + } + } +} + +void KMFolderCachedImap::slotRescueDone(KMCommand * command) +{ + // FIXME: check command result + if ( command ) + --mRescueCommandCount; + if ( mRescueCommandCount > 0 ) + return; + for ( QValueList<KMFolder*>::ConstIterator it = mToBeDeletedAfterRescue.constBegin(); + it != mToBeDeletedAfterRescue.constEnd(); ++it ) { + kmkernel->dimapFolderMgr()->remove( *it ); + } + mToBeDeletedAfterRescue.clear(); + serverSyncInternal(); +} + +#include "kmfoldercachedimap.moc" |