kmail

kmfoldercachedimap.cpp

Go to the documentation of this file.
00001 
00032 #ifdef HAVE_CONFIG_H
00033 #include <config.h>
00034 #endif
00035 
00036 #include <errno.h>
00037 
00038 #include <qvaluevector.h>
00039 
00040 #include "kmkernel.h"
00041 #include "kmfoldercachedimap.h"
00042 #include "undostack.h"
00043 #include "kmfoldermgr.h"
00044 #include "kmacctcachedimap.h"
00045 #include "accountmanager.h"
00046 using KMail::AccountManager;
00047 #include "kmailicalifaceimpl.h"
00048 #include "kmfolder.h"
00049 #include "kmglobal.h"
00050 #include "acljobs.h"
00051 #include "broadcaststatus.h"
00052 using KPIM::BroadcastStatus;
00053 #include "progressmanager.h"
00054 
00055 using KMail::CachedImapJob;
00056 #include "imapaccountbase.h"
00057 using KMail::ImapAccountBase;
00058 #include "listjob.h"
00059 using KMail::ListJob;
00060 
00061 #include "kmfolderseldlg.h"
00062 #include "kmcommands.h"
00063 #include "kmmainwidget.h"
00064 
00065 #include <kapplication.h>
00066 #include <kmessagebox.h>
00067 #include <klocale.h>
00068 #include <kdebug.h>
00069 #include <kconfig.h>
00070 #include <kio/global.h>
00071 #include <kio/scheduler.h>
00072 #include <qbuffer.h>
00073 #include <qbuttongroup.h>
00074 #include <qcombobox.h>
00075 #include <qfile.h>
00076 #include <qhbox.h>
00077 #include <qlabel.h>
00078 #include <qlayout.h>
00079 #include <qradiobutton.h>
00080 #include <qvaluelist.h>
00081 #include "annotationjobs.h"
00082 #include "quotajobs.h"
00083 using namespace KMail;
00084 #include <globalsettings.h>
00085 
00086 #define UIDCACHE_VERSION 1
00087 #define MAIL_LOSS_DEBUGGING 0
00088 
00089 static QString incidencesForToString( KMFolderCachedImap::IncidencesFor r ) {
00090   switch (r) {
00091   case KMFolderCachedImap::IncForNobody: return "nobody";
00092   case KMFolderCachedImap::IncForAdmins: return "admins";
00093   case KMFolderCachedImap::IncForReaders: return "readers";
00094   }
00095   return QString::null; // can't happen
00096 }
00097 
00098 static KMFolderCachedImap::IncidencesFor incidencesForFromString( const QString& str ) {
00099   if ( str == "nobody" ) return KMFolderCachedImap::IncForNobody;
00100   if ( str == "admins" ) return KMFolderCachedImap::IncForAdmins;
00101   if ( str == "readers" ) return KMFolderCachedImap::IncForReaders;
00102   return KMFolderCachedImap::IncForAdmins; // by default
00103 }
00104 
00105 DImapTroubleShootDialog::DImapTroubleShootDialog( QWidget* parent,
00106                                                   const char* name )
00107   : KDialogBase( Plain, i18n( "Troubleshooting IMAP Cache" ),
00108                  Ok | Cancel, Cancel, parent, name, true ),
00109     rc( None )
00110 {
00111   QFrame* page = plainPage();
00112   QVBoxLayout *topLayout = new QVBoxLayout( page, 0 );
00113   // spell "lose" correctly. but don't cause a fuzzy.
00114   QString txt = i18n( "<p><b>Troubleshooting the IMAP cache.</b></p>"
00115                       "<p>If you have problems with synchronizing an IMAP "
00116                       "folder, you should first try rebuilding the index "
00117                       "file. This will take some time to rebuild, but will "
00118                       "not cause any problems.</p><p>If that is not enough, "
00119                       "you can try refreshing the IMAP cache. If you do this, "
00120                       "you will loose all your local changes for this folder "
00121                       "and all its subfolders.</p>",
00122                       "<p><b>Troubleshooting the IMAP cache.</b></p>"
00123                       "<p>If you have problems with synchronizing an IMAP "
00124                       "folder, you should first try rebuilding the index "
00125                       "file. This will take some time to rebuild, but will "
00126                       "not cause any problems.</p><p>If that is not enough, "
00127                       "you can try refreshing the IMAP cache. If you do this, "
00128                       "you will lose all your local changes for this folder "
00129                       "and all its subfolders.</p>" );
00130   topLayout->addWidget( new QLabel( txt, page ) );
00131 
00132   QButtonGroup *group = new QButtonGroup( 0 );
00133 
00134   mIndexButton = new QRadioButton( page );
00135   mIndexButton->setText( i18n( "Rebuild &Index" ) );
00136   group->insert( mIndexButton );
00137   topLayout->addWidget( mIndexButton );
00138 
00139   QHBox *hbox = new QHBox( page );
00140   QLabel *scopeLabel = new QLabel( i18n( "Scope:" ), hbox );
00141   scopeLabel->setEnabled( false );
00142   mIndexScope = new QComboBox( hbox );
00143   mIndexScope->insertItem( i18n( "Only current folder" ) );
00144   mIndexScope->insertItem( i18n( "Current folder and all subfolders" ) );
00145   mIndexScope->insertItem( i18n( "All folders of this account" ) );
00146   mIndexScope->setEnabled( false );
00147   topLayout->addWidget( hbox );
00148 
00149   mCacheButton = new QRadioButton( page );
00150   mCacheButton->setText( i18n( "Refresh &Cache" ) );
00151   group->insert( mCacheButton );
00152   topLayout->addWidget( mCacheButton );
00153 
00154   enableButtonSeparator( true );
00155 
00156   connect ( mIndexButton, SIGNAL(toggled(bool)), mIndexScope, SLOT(setEnabled(bool)) );
00157   connect ( mIndexButton, SIGNAL(toggled(bool)), scopeLabel, SLOT(setEnabled(bool)) );
00158 
00159   connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) );
00160 }
00161 
00162 int DImapTroubleShootDialog::run()
00163 {
00164   DImapTroubleShootDialog d;
00165   d.exec();
00166   return d.rc;
00167 }
00168 
00169 void DImapTroubleShootDialog::slotDone()
00170 {
00171   rc = None;
00172   if ( mIndexButton->isOn() )
00173     rc = mIndexScope->currentItem();
00174   else if ( mCacheButton->isOn() )
00175     rc = RefreshCache;
00176   done( Ok );
00177 }
00178 
00179 KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName )
00180   : KMFolderMaildir( folder, aName ),
00181     mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ),
00182     mSubfolderState( imapNoInformation ),
00183     mIncidencesFor( IncForAdmins ),
00184     mIsSelected( false ),
00185     mCheckFlags( true ), mReadOnly( false ), mAccount( NULL ), uidMapDirty( true ),
00186     uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ),
00187     mFoundAnIMAPDigest( false ),
00188     mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ),
00189     /*mHoldSyncs( false ),*/
00190     mFolderRemoved( false ),
00191     mRecurse( true ),
00192     mStatusChangedLocally( false ), mAnnotationFolderTypeChanged( false ),
00193     mIncidencesForChanged( false ), mPersonalNamespacesCheckDone( true ),
00194     mQuotaInfo(), mAlarmsBlocked( false ),
00195     mRescueCommandCount( 0 ),
00196     mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values)
00197 {
00198   setUidValidity("");
00199   // if we fail to read a uid file but there is one, nuke it
00200   if ( readUidCache() == -1 ) {
00201     if ( QFile::exists( uidCacheLocation() ) ) {
00202         KMessageBox::error( 0,
00203         i18n( "The UID cache file for folder %1 could not be read. There "
00204               "could be a problem with file system permission, or it is corrupted."
00205               ).arg( folder->prettyURL() ) );
00206         // try to unlink it, in case it was corruped. If it couldn't be read
00207         // because of permissions, this will fail, which is fine
00208         unlink( QFile::encodeName( uidCacheLocation() ) );
00209     }
00210   }
00211 
00212   mProgress = 0;
00213 }
00214 
00215 KMFolderCachedImap::~KMFolderCachedImap()
00216 {
00217   if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
00218 }
00219 
00220 void KMFolderCachedImap::reallyDoClose( const char* owner )
00221 {
00222   if( !mFolderRemoved ) {
00223     writeUidCache();
00224   }
00225   KMFolderMaildir::reallyDoClose( owner );
00226 }
00227 
00228 void KMFolderCachedImap::initializeFrom( KMFolderCachedImap* parent )
00229 {
00230   setAccount( parent->account() );
00231   // Now that we have an account, tell it that this folder was created:
00232   // if this folder was just removed, then we don't really want to remove it from the server.
00233   mAccount->removeDeletedFolder( imapPath() );
00234   setUserRights( parent->userRights() );
00235 }
00236 
00237 void KMFolderCachedImap::readConfig()
00238 {
00239   KConfig* config = KMKernel::config();
00240   KConfigGroupSaver saver( config, "Folder-" + folder()->idString() );
00241   if( mImapPath.isEmpty() ) mImapPath = config->readEntry( "ImapPath" );
00242   if( QString( name() ).upper() == "INBOX" && mImapPath == "/INBOX/" )
00243   {
00244     folder()->setLabel( i18n( "inbox" ) );
00245     // for the icon
00246     folder()->setSystemFolder( true );
00247   }
00248   mNoContent = config->readBoolEntry( "NoContent", false );
00249   mReadOnly = config->readBoolEntry( "ReadOnly", false );
00250   if ( !config->readEntry( "FolderAttributes" ).isEmpty() )
00251     mFolderAttributes = config->readEntry( "FolderAttributes" );
00252 
00253   if ( mAnnotationFolderType != "FROMSERVER" ) {
00254     mAnnotationFolderType = config->readEntry( "Annotation-FolderType" );
00255     // if there is an annotation, it has to be XML
00256     if ( !mAnnotationFolderType.isEmpty() && !mAnnotationFolderType.startsWith( "mail" ) )
00257       kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML );
00258 //    kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
00259 //                  << " readConfig: mAnnotationFolderType=" << mAnnotationFolderType << endl;
00260   }
00261   mIncidencesFor = incidencesForFromString( config->readEntry( "IncidencesFor" ) );
00262   mAlarmsBlocked = config->readBoolEntry( "AlarmsBlocked", false );
00263 //  kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
00264 //                << " readConfig: mIncidencesFor=" << mIncidencesFor << endl;
00265 
00266   mUserRights = config->readNumEntry( "UserRights", 0 ); // default is we don't know
00267   mOldUserRights = mUserRights;
00268 
00269   int storageQuotaUsage = config->readNumEntry( "StorageQuotaUsage", -1 );
00270   int storageQuotaLimit = config->readNumEntry( "StorageQuotaLimit", -1 );
00271   QString storageQuotaRoot = config->readEntry( "StorageQuotaRoot", QString::null );
00272   if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set
00273       mQuotaInfo.setName( "STORAGE" );
00274       mQuotaInfo.setRoot( storageQuotaRoot );
00275 
00276       if ( storageQuotaUsage > -1 )
00277         mQuotaInfo.setCurrent( storageQuotaUsage );
00278       if ( storageQuotaLimit > -1 )
00279         mQuotaInfo.setMax( storageQuotaLimit );
00280   }
00281 
00282   KMFolderMaildir::readConfig();
00283 
00284   mStatusChangedLocally =
00285     config->readBoolEntry( "StatusChangedLocally", false );
00286 
00287   mAnnotationFolderTypeChanged = config->readBoolEntry( "AnnotationFolderTypeChanged", false );
00288   mIncidencesForChanged = config->readBoolEntry( "IncidencesForChanged", false );
00289   if ( mImapPath.isEmpty() ) {
00290     mImapPathCreation = config->readEntry("ImapPathCreation");
00291   }
00292 
00293   QStringList uids = config->readListEntry( "UIDSDeletedSinceLastSync" );
00294 #if MAIL_LOSS_DEBUGGING
00295   kdDebug( 5006 ) << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyURL() << endl << uids << endl;
00296 #endif
00297   for ( QStringList::iterator it = uids.begin(); it != uids.end(); it++ ) {
00298       mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0);
00299   }
00300 }
00301 
00302 void KMFolderCachedImap::writeConfig()
00303 {
00304   // don't re-write the config of a removed folder, this has just been deleted in
00305   // the folder manager
00306   if ( mFolderRemoved )
00307     return;
00308 
00309   KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
00310   configGroup.writeEntry( "ImapPath", mImapPath );
00311   configGroup.writeEntry( "NoContent", mNoContent );
00312   configGroup.writeEntry( "ReadOnly", mReadOnly );
00313   configGroup.writeEntry( "FolderAttributes", mFolderAttributes );
00314   configGroup.writeEntry( "StatusChangedLocally", mStatusChangedLocally );
00315   if ( !mImapPathCreation.isEmpty() ) {
00316     if ( mImapPath.isEmpty() ) {
00317       configGroup.writeEntry( "ImapPathCreation", mImapPathCreation );
00318     } else {
00319       configGroup.deleteEntry( "ImapPathCreation" );
00320     }
00321   }
00322   if ( !mDeletedUIDsSinceLastSync.isEmpty() ) {
00323       QValueList<ulong> uids = mDeletedUIDsSinceLastSync.keys();
00324       QStringList uidstrings;
00325       for( QValueList<ulong>::iterator it = uids.begin(); it != uids.end(); it++ ) {
00326           uidstrings.append(  QString::number( (*it) ) );
00327       }
00328       configGroup.writeEntry( "UIDSDeletedSinceLastSync", uidstrings );
00329 #if MAIL_LOSS_DEBUGGING
00330       kdDebug( 5006 ) << "WRITING OUT UIDSDeletedSinceLastSync in: " << folder( )->prettyURL( ) << endl << uidstrings << endl;
00331 #endif
00332   } else {
00333     configGroup.deleteEntry( "UIDSDeletedSinceLastSync" );
00334   }
00335   writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
00336   KMFolderMaildir::writeConfig();
00337 }
00338 
00339 void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig()
00340 {
00341   KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
00342   if ( !folder()->noContent() )
00343   {
00344     configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged );
00345     configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType );
00346     configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged );
00347     configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) );
00348     configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked );
00349     configGroup.writeEntry( "UserRights", mUserRights );
00350 
00351     configGroup.deleteEntry( "StorageQuotaUsage");
00352     configGroup.deleteEntry( "StorageQuotaRoot");
00353     configGroup.deleteEntry( "StorageQuotaLimit");
00354 
00355     if ( mQuotaInfo.isValid() ) {
00356       if ( mQuotaInfo.current().isValid() ) {
00357         configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() );
00358       }
00359       if ( mQuotaInfo.max().isValid() ) {
00360         configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() );
00361       }
00362       configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() );
00363     }
00364   }
00365 }
00366 
00367 int KMFolderCachedImap::create()
00368 {
00369   int rc = KMFolderMaildir::create();
00370   // FIXME why the below? - till
00371   readConfig();
00372   mUnreadMsgs = -1;
00373   return rc;
00374 }
00375 
00376 void KMFolderCachedImap::remove()
00377 {
00378   mFolderRemoved = true;
00379 
00380   QString part1 = folder()->path() + "/." + dotEscape(name());
00381   QString uidCacheFile = part1 + ".uidcache";
00382   // This is the account folder of an account that was just removed
00383   // When this happens, be sure to delete all traces of the cache
00384   if( QFile::exists(uidCacheFile) )
00385     unlink( QFile::encodeName( uidCacheFile ) );
00386 
00387   FolderStorage::remove();
00388 }
00389 
00390 QString KMFolderCachedImap::uidCacheLocation() const
00391 {
00392   QString sLocation(folder()->path());
00393   if (!sLocation.isEmpty()) sLocation += '/';
00394   return sLocation + '.' + dotEscape(fileName()) + ".uidcache";
00395 }
00396 
00397 int KMFolderCachedImap::readUidCache()
00398 {
00399   QFile uidcache( uidCacheLocation() );
00400   if( uidcache.open( IO_ReadOnly ) ) {
00401     char buf[1024];
00402     int len = uidcache.readLine( buf, sizeof(buf) );
00403     if( len > 0 ) {
00404       int cacheVersion;
00405       sscanf( buf, "# KMail-UidCache V%d\n",  &cacheVersion );
00406       if( cacheVersion == UIDCACHE_VERSION ) {
00407         len = uidcache.readLine( buf, sizeof(buf) );
00408         if( len > 0 ) {
00409           setUidValidity( QString::fromLocal8Bit(buf).stripWhiteSpace() );
00410           len = uidcache.readLine( buf, sizeof(buf) );
00411           if( len > 0 ) {
00412 #if MAIL_LOSS_DEBUGGING
00413             kdDebug(5006) << "Reading in last uid from cache: " << QString::fromLocal8Bit(buf).stripWhiteSpace() << " in " << folder()->prettyURL() << endl;
00414 #endif
00415             // load the last known highest uid from the on disk cache
00416             setLastUid( QString::fromLocal8Bit(buf).stripWhiteSpace().toULong() );
00417             return 0;
00418           }
00419         }
00420       }
00421     }
00422   }
00423   return -1;
00424 }
00425 
00426 int KMFolderCachedImap::writeUidCache()
00427 {
00428   if( uidValidity().isEmpty() || uidValidity() == "INVALID" ) {
00429     // No info from the server yet, remove the file.
00430     if( QFile::exists( uidCacheLocation() ) )
00431       return unlink( QFile::encodeName( uidCacheLocation() ) );
00432     return 0;
00433   }
00434 #if MAIL_LOSS_DEBUGGING
00435   kdDebug(5006) << "Writing out UID cache lastuid: " << lastUid()  << " in: " << folder()->prettyURL() << endl;
00436 #endif
00437   QFile uidcache( uidCacheLocation() );
00438   if( uidcache.open( IO_WriteOnly ) ) {
00439     QTextStream str( &uidcache );
00440     str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl;
00441     str << uidValidity() << endl;
00442     str << lastUid() << endl;
00443     uidcache.flush();
00444     if ( uidcache.status() == IO_Ok ) {
00445       fsync( uidcache.handle() ); /* this is probably overkill */
00446       uidcache.close();
00447       if ( uidcache.status() == IO_Ok )
00448         return 0;
00449     }
00450   }
00451   KMessageBox::error( 0,
00452         i18n( "The UID cache file for folder %1 could not be written. There "
00453               "could be a problem with file system permission." ).arg( folder()->prettyURL() ) );
00454 
00455   return -1;
00456 }
00457 
00458 void KMFolderCachedImap::reloadUidMap()
00459 {
00460   //kdDebug(5006) << "Reloading Uid Map " << endl;
00461   uidMap.clear();
00462   open("reloadUdi");
00463   for( int i = 0; i < count(); ++i ) {
00464     KMMsgBase *msg = getMsgBase( i );
00465     if( !msg ) continue;
00466     ulong uid = msg->UID();
00467     //kdDebug(5006) << "Inserting: " << i << " with uid: " << uid << endl;
00468     uidMap.insert( uid, i );
00469   }
00470   close("reloadUdi");
00471   uidMapDirty = false;
00472 }
00473 
00474 KMMessage* KMFolderCachedImap::take(int idx)
00475 {
00476   uidMapDirty = true;
00477   rememberDeletion( idx );
00478   return KMFolderMaildir::take(idx);
00479 }
00480 
00481 void KMFolderCachedImap::takeTemporarily( int idx )
00482 {
00483   KMFolderMaildir::take( idx );
00484 }
00485 
00486 // Add a message without clearing it's X-UID field.
00487 int KMFolderCachedImap::addMsgInternal( KMMessage* msg, bool newMail,
00488                                         int* index_return )
00489 {
00490   // Possible optimization: Only dirty if not filtered below
00491   ulong uid = msg->UID();
00492   if( uid != 0 ) {
00493     uidMapDirty = true;
00494   }
00495 
00496   KMFolderOpener openThis(folder(), "KMFolderCachedImap::addMsgInternal");
00497   int rc = openThis.openResult();
00498   if ( rc ) {
00499     kdDebug(5006) << k_funcinfo << "open: " << rc << " of folder: " << label() << endl;
00500     return rc;
00501   }
00502 
00503   // Add the message
00504   rc = KMFolderMaildir::addMsg(msg, index_return);
00505 
00506   if( newMail && ( imapPath() == "/INBOX/" || ( !GlobalSettings::self()->filterOnlyDIMAPInbox()
00507       && (userRights() <= 0 || userRights() & ACLJobs::Administer )
00508       && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) )
00509     // This is a new message. Filter it
00510     mAccount->processNewMsg( msg );
00511 
00512   return rc;
00513 }
00514 
00515 /* Reimplemented from KMFolderMaildir */
00516 int KMFolderCachedImap::addMsg(KMMessage* msg, int* index_return)
00517 {
00518   if ( !canAddMsgNow( msg, index_return ) ) return 0;
00519   // Add it to storage
00520   int rc = KMFolderMaildir::addMsgInternal(msg, index_return, true /*stripUID*/);
00521   return rc;
00522 }
00523 
00524 void KMFolderCachedImap::rememberDeletion( int idx )
00525 {
00526   KMMsgBase *msg = getMsgBase( idx );
00527   assert(msg);
00528   long uid = msg->UID();
00529   assert(uid>=0);
00530   mDeletedUIDsSinceLastSync.insert(uid, 0);
00531   kdDebug(5006) << "Explicit delete of UID " << uid << " at index: " << idx << " in " << folder()->prettyURL() << endl;
00532 }
00533 
00534 /* Reimplemented from KMFolderMaildir */
00535 void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet)
00536 {
00537   uidMapDirty = true;
00538   rememberDeletion( idx );
00539   // Remove it from disk
00540   KMFolderMaildir::removeMsg(idx,imapQuiet);
00541 }
00542 
00543 bool KMFolderCachedImap::canRemoveFolder() const {
00544   // If this has subfolders it can't be removed
00545   if( folder() && folder()->child() && folder()->child()->count() > 0 )
00546     return false;
00547 
00548 #if 0
00549   // No special condition here, so let base class decide
00550   return KMFolderMaildir::canRemoveFolder();
00551 #endif
00552   return true;
00553 }
00554 
00555 /* Reimplemented from KMFolderDir */
00556 int KMFolderCachedImap::rename( const QString& aName,
00557                                 KMFolderDir* /*aParent*/ )
00558 {
00559   QString oldName = mAccount->renamedFolder( imapPath() );
00560   if ( oldName.isEmpty() ) oldName = name();
00561   if ( aName == oldName )
00562     // Stupid user trying to rename it to it's old name :)
00563     return 0;
00564 
00565   if( account() == 0 || imapPath().isEmpty() ) { // I don't think any of this can happen anymore
00566     QString err = i18n("You must synchronize with the server before renaming IMAP folders.");
00567     KMessageBox::error( 0, err );
00568     return -1;
00569   }
00570 
00571   // Make the change appear to the user with setLabel, but we'll do the change
00572   // on the server during the next sync. The name() is the name at the time of
00573   // the last sync. Only rename if the new one is different. If it's the same,
00574   // don't rename, but also make sure the rename is reset, in the case of
00575   // A -> B -> A renames.
00576   if ( name() != aName )
00577     mAccount->addRenamedFolder( imapPath(), folder()->label(), aName );
00578   else
00579     mAccount->removeRenamedFolder( imapPath() );
00580 
00581   folder()->setLabel( aName );
00582   emit nameChanged(); // for kmailicalifaceimpl
00583 
00584   return 0;
00585 }
00586 
00587 KMFolder* KMFolderCachedImap::trashFolder() const
00588 {
00589   QString trashStr = account()->trash();
00590   return kmkernel->dimapFolderMgr()->findIdString( trashStr );
00591 }
00592 
00593 void KMFolderCachedImap::setLastUid( ulong uid )
00594 {
00595 #if MAIL_LOSS_DEBUGGING
00596   kdDebug(5006) << "Setting mLastUid to: " << uid  <<  " in " << folder()->prettyURL() << endl;
00597 #endif
00598   mLastUid = uid;
00599   if( uidWriteTimer == -1 )
00600     // Write in one minute
00601     uidWriteTimer = startTimer( 60000 );
00602 }
00603 
00604 void KMFolderCachedImap::timerEvent( QTimerEvent* )
00605 {
00606   killTimer( uidWriteTimer );
00607   uidWriteTimer = -1;
00608   if ( writeUidCache() == -1 )
00609     unlink( QFile::encodeName( uidCacheLocation() ) );
00610 }
00611 
00612 ulong KMFolderCachedImap::lastUid()
00613 {
00614   return mLastUid;
00615 }
00616 
00617 KMMsgBase* KMFolderCachedImap::findByUID( ulong uid )
00618 {
00619   bool mapReloaded = false;
00620   if( uidMapDirty ) {
00621     reloadUidMap();
00622     mapReloaded = true;
00623   }
00624 
00625   QMap<ulong,int>::Iterator it = uidMap.find( uid );
00626   if( it != uidMap.end() ) {
00627     KMMsgBase *msg = getMsgBase( *it );
00628 #if MAIL_LOSS_DEBUGGING
00629     kdDebug(5006) << "Folder: " << folder()->prettyURL() << endl;
00630     kdDebug(5006) << "UID " << uid << " is supposed to be in the map" << endl;
00631     kdDebug(5006) << "UID's index is to be " << *it << endl;
00632     kdDebug(5006) << "There is a message there? " << (msg != 0) << endl;
00633     if ( msg ) {
00634       kdDebug(5006) << "Its UID is: " << msg->UID() << endl;
00635     }
00636 #endif
00637 
00638     if( msg && msg->UID() == uid )
00639       return msg;
00640     kdDebug(5006) << "########## Didn't find uid: " << uid << "in cache athough it's supposed to be there!" << endl;
00641   } else {
00642 #if MAIL_LOSS_DEBUGGING
00643     kdDebug(5006) << "Didn't find uid: " << uid << "in cache!" << endl;
00644 #endif
00645   }
00646   // Not found by now
00647  // if( mapReloaded )
00648     // Not here then
00649     return 0;
00650   // There could be a problem in the maps. Rebuild them and try again
00651   reloadUidMap();
00652   it = uidMap.find( uid );
00653   if( it != uidMap.end() )
00654     // Since the uid map is just rebuilt, no need for the sanity check
00655     return getMsgBase( *it );
00656 #if MAIL_LOSS_DEBUGGING
00657   else
00658     kdDebug(5006) << "Reloaded, but stil didn't find uid: " << uid << endl;
00659 #endif
00660   // Then it's not here
00661   return 0;
00662 }
00663 
00664 // This finds and sets the proper account for this folder if it has
00665 // not been done
00666 KMAcctCachedImap *KMFolderCachedImap::account() const
00667 {
00668   if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) {
00669     // Find the account
00670     mAccount = static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( name() ) );
00671   }
00672 
00673   return mAccount;
00674 }
00675 
00676 void KMFolderCachedImap::slotTroubleshoot()
00677 {
00678   const int rc = DImapTroubleShootDialog::run();
00679 
00680   if( rc == DImapTroubleShootDialog::RefreshCache ) {
00681     // Refresh cache
00682     if( !account() ) {
00683       KMessageBox::sorry( 0, i18n("No account setup for this folder.\n"
00684                                   "Please try running a sync before this.") );
00685       return;
00686     }
00687     QString str = i18n("Are you sure you want to refresh the IMAP cache of "
00688                        "the folder %1 and all its subfolders?\nThis will "
00689                        "remove all changes you have done locally to your "
00690                        "folders.").arg( label() );
00691     QString s1 = i18n("Refresh IMAP Cache");
00692     QString s2 = i18n("&Refresh");
00693     if( KMessageBox::warningContinueCancel( 0, str, s1, s2 ) ==
00694         KMessageBox::Continue )
00695       account()->invalidateIMAPFolders( this );
00696   } else {
00697     // Rebuild index file
00698     switch ( rc ) {
00699       case DImapTroubleShootDialog::ReindexAll:
00700       {
00701         KMFolderCachedImap *rootStorage = dynamic_cast<KMFolderCachedImap*>( account()->rootFolder() );
00702         if ( rootStorage )
00703           rootStorage->createIndexFromContentsRecursive();
00704         break;
00705       }
00706       case DImapTroubleShootDialog::ReindexCurrent:
00707         createIndexFromContents();
00708         break;
00709       case DImapTroubleShootDialog::ReindexRecursive:
00710         createIndexFromContentsRecursive();
00711         break;
00712       default:
00713         return;
00714     }
00715     KMessageBox::information( 0, i18n( "The index of this folder has been "
00716                                        "recreated." ) );
00717     writeIndex();
00718     kmkernel->getKMMainWidget()->folderSelected();
00719   }
00720 }
00721 
00722 void KMFolderCachedImap::serverSync( bool recurse )
00723 {
00724   if( mSyncState != SYNC_STATE_INITIAL ) {
00725     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 ) {
00726       mSyncState = SYNC_STATE_INITIAL;
00727     } else return;
00728   }
00729 
00730   mRecurse = recurse;
00731   assert( account() );
00732 
00733   ProgressItem *progressItem = mAccount->mailCheckProgressItem();
00734   if ( progressItem ) {
00735     progressItem->reset();
00736     progressItem->setTotalItems( 100 );
00737   }
00738   mProgress = 0;
00739 
00740 #if 0
00741   if( mHoldSyncs ) {
00742     // All done for this folder.
00743     account()->mailCheckProgressItem()->setProgress( 100 );
00744     mProgress = 100; // all done
00745     newState( mProgress, i18n("Synchronization skipped"));
00746     mSyncState = SYNC_STATE_INITIAL;
00747     emit folderComplete( this, true );
00748     return;
00749   }
00750 #endif
00751   mTentativeHighestUid = 0; // reset, last sync could have been canceled
00752 
00753   serverSyncInternal();
00754 }
00755 
00756 QString KMFolderCachedImap::state2String( int state ) const
00757 {
00758   switch( state ) {
00759   case SYNC_STATE_INITIAL:           return "SYNC_STATE_INITIAL";
00760   case SYNC_STATE_GET_USERRIGHTS:    return "SYNC_STATE_GET_USERRIGHTS";
00761   case SYNC_STATE_PUT_MESSAGES:      return "SYNC_STATE_PUT_MESSAGES";
00762   case SYNC_STATE_UPLOAD_FLAGS:      return "SYNC_STATE_UPLOAD_FLAGS";
00763   case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS";
00764   case SYNC_STATE_LIST_SUBFOLDERS:   return "SYNC_STATE_LIST_SUBFOLDERS";
00765   case SYNC_STATE_LIST_NAMESPACES:   return "SYNC_STATE_LIST_NAMESPACES";
00766   case SYNC_STATE_LIST_SUBFOLDERS2:  return "SYNC_STATE_LIST_SUBFOLDERS2";
00767   case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS";
00768   case SYNC_STATE_LIST_MESSAGES:     return "SYNC_STATE_LIST_MESSAGES";
00769   case SYNC_STATE_DELETE_MESSAGES:   return "SYNC_STATE_DELETE_MESSAGES";
00770   case SYNC_STATE_GET_MESSAGES:      return "SYNC_STATE_GET_MESSAGES";
00771   case SYNC_STATE_EXPUNGE_MESSAGES:  return "SYNC_STATE_EXPUNGE_MESSAGES";
00772   case SYNC_STATE_HANDLE_INBOX:      return "SYNC_STATE_HANDLE_INBOX";
00773   case SYNC_STATE_TEST_ANNOTATIONS:  return "SYNC_STATE_TEST_ANNOTATIONS";
00774   case SYNC_STATE_GET_ANNOTATIONS:   return "SYNC_STATE_GET_ANNOTATIONS";
00775   case SYNC_STATE_SET_ANNOTATIONS:   return "SYNC_STATE_SET_ANNOTATIONS";
00776   case SYNC_STATE_GET_ACLS:          return "SYNC_STATE_GET_ACLS";
00777   case SYNC_STATE_SET_ACLS:          return "SYNC_STATE_SET_ACLS";
00778   case SYNC_STATE_GET_QUOTA:         return "SYNC_STATE_GET_QUOTA";
00779   case SYNC_STATE_FIND_SUBFOLDERS:   return "SYNC_STATE_FIND_SUBFOLDERS";
00780   case SYNC_STATE_SYNC_SUBFOLDERS:   return "SYNC_STATE_SYNC_SUBFOLDERS";
00781   case SYNC_STATE_RENAME_FOLDER:     return "SYNC_STATE_RENAME_FOLDER";
00782   case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY";
00783   default:                           return "Unknown state";
00784   }
00785 }
00786 
00787 /*
00788   Progress calculation: each step is assigned a span. Initially the total is 100.
00789   But if we skip a step, don't increase the progress.
00790   This leaves more room for the step a with variable size (get_messages)
00791    connecting 5
00792    getuserrights 5
00793    rename 5
00794    check_uidvalidity 5
00795    create_subfolders 5
00796    put_messages 10 (but it can take a very long time, with many messages....)
00797    upload_flags 5
00798    list_subfolders 5
00799    list_subfolders2 0 (all local)
00800    delete_subfolders 5
00801    list_messages 10
00802    delete_messages 10
00803    expunge_messages 5
00804    get_messages variable (remaining-5) i.e. minimum 15.
00805    check_annotations 0 (rare)
00806    set_annotations 0 (rare)
00807    get_annotations 2
00808    set_acls 0 (rare)
00809    get_acls 3
00810 
00811   noContent folders have only a few of the above steps
00812   (permissions, and all subfolder stuff), so its steps should be given more span
00813 
00814  */
00815 
00816 // While the server synchronization is running, mSyncState will hold
00817 // the state that should be executed next
00818 void KMFolderCachedImap::serverSyncInternal()
00819 {
00820   // This is used to stop processing when we're about to exit
00821   // and the current job wasn't cancellable.
00822   // For user-requested abort, we'll use signalAbortRequested instead.
00823   if( kmkernel->mailCheckAborted() ) {
00824     resetSyncState();
00825     emit folderComplete( this, false );
00826     return;
00827   }
00828 
00829   //kdDebug(5006) << label() << ": " << state2String( mSyncState ) << endl;
00830   switch( mSyncState ) {
00831   case SYNC_STATE_INITIAL:
00832   {
00833     mProgress = 0;
00834     foldersForDeletionOnServer.clear();
00835     newState( mProgress, i18n("Synchronizing"));
00836 
00837     open("cachedimap");
00838     if ( !noContent() )
00839         mAccount->addLastUnreadMsgCount( this, countUnread() );
00840 
00841     // Connect to the server (i.e. prepare the slave)
00842     ImapAccountBase::ConnectionState cs = mAccount->makeConnection();
00843     if ( cs == ImapAccountBase::Error ) {
00844       // Cancelled by user, or slave can't start
00845       // kdDebug(5006) << "makeConnection said Error, aborting." << endl;
00846       // We stop here. We're already in SYNC_STATE_INITIAL for the next time.
00847       newState( mProgress, i18n( "Error connecting to server %1" ).arg( mAccount->host() ) );
00848       close("cachedimap");
00849       emit folderComplete(this, false);
00850       break;
00851     } else if ( cs == ImapAccountBase::Connecting ) {
00852       mAccount->setAnnotationCheckPassed( false );
00853       // kdDebug(5006) << "makeConnection said Connecting, waiting for signal." << endl;
00854       newState( mProgress, i18n("Connecting to %1").arg( mAccount->host() ) );
00855       // We'll wait for the connectionResult signal from the account.
00856       connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
00857                this, SLOT( slotConnectionResult(int, const QString&) ) );
00858       break;
00859     } else {
00860       // Connected
00861       // kdDebug(5006) << "makeConnection said Connected, proceeding." << endl;
00862       mSyncState = SYNC_STATE_GET_USERRIGHTS;
00863       // Fall through to next state
00864     }
00865   }
00866 
00867 
00868   case SYNC_STATE_GET_USERRIGHTS:
00869     //kdDebug(5006) << "===== Syncing " << ( mImapPath.isEmpty() ? label() : mImapPath ) << endl;
00870 
00871     mSyncState = SYNC_STATE_RENAME_FOLDER;
00872 
00873     if( !noContent() && mAccount->hasACLSupport() ) {
00874       // Check the user's own rights. We do this every time in case they changed.
00875       mOldUserRights = mUserRights;
00876       newState( mProgress, i18n("Checking permissions"));
00877       connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
00878                this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
00879       mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case
00880       break;
00881     }
00882 
00883   case SYNC_STATE_RENAME_FOLDER:
00884   {
00885     mSyncState = SYNC_STATE_CHECK_UIDVALIDITY;
00886     // Returns the new name if the folder was renamed, empty otherwise.
00887     bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() );
00888     QString newName = mAccount->renamedFolder( imapPath() );
00889     if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) {
00890       newState( mProgress, i18n("Renaming folder") );
00891       CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this );
00892       connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
00893       connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00894       job->start();
00895       break;
00896     }
00897   }
00898 
00899   case SYNC_STATE_CHECK_UIDVALIDITY:
00900     mSyncState = SYNC_STATE_CREATE_SUBFOLDERS;
00901     if( !noContent() ) {
00902       checkUidValidity();
00903       break;
00904     }
00905     // Else carry on
00906 
00907   case SYNC_STATE_CREATE_SUBFOLDERS:
00908     mSyncState = SYNC_STATE_PUT_MESSAGES;
00909     createNewFolders();
00910     break;
00911 
00912   case SYNC_STATE_PUT_MESSAGES:
00913     mSyncState = SYNC_STATE_UPLOAD_FLAGS;
00914     if( !noContent() ) {
00915       uploadNewMessages();
00916       break;
00917     }
00918     // Else carry on
00919   case SYNC_STATE_UPLOAD_FLAGS:
00920     mSyncState = SYNC_STATE_LIST_NAMESPACES;
00921     if( !noContent() ) {
00922        // We haven't downloaded messages yet, so we need to build the map.
00923        if( uidMapDirty )
00924          reloadUidMap();
00925        // Upload flags, unless we know from the ACL that we're not allowed
00926        // to do that or they did not change locally
00927        if ( mUserRights <= 0 || ( mUserRights & (KMail::ACLJobs::WriteFlags ) ) ) {
00928          if ( mStatusChangedLocally ) {
00929            uploadFlags();
00930            break;
00931          } else {
00932            //kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl;
00933          }
00934        } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
00935          if ( mStatusChangedLocally ) {
00936            uploadSeenFlags();
00937            break;
00938          }
00939        }
00940     }
00941     // Else carry on
00942 
00943   case SYNC_STATE_LIST_NAMESPACES:
00944     if ( this == mAccount->rootFolder() ) {
00945       listNamespaces();
00946       break;
00947     }
00948     mSyncState = SYNC_STATE_LIST_SUBFOLDERS;
00949     // Else carry on
00950 
00951   case SYNC_STATE_LIST_SUBFOLDERS:
00952     newState( mProgress, i18n("Retrieving folderlist"));
00953     mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
00954     if( !listDirectory() ) {
00955       mSyncState = SYNC_STATE_INITIAL;
00956       KMessageBox::error(0, i18n("Error while retrieving the folderlist"));
00957     }
00958     break;
00959 
00960   case SYNC_STATE_LIST_SUBFOLDERS2:
00961     mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
00962     mProgress += 10;
00963     newState( mProgress, i18n("Retrieving subfolders"));
00964     listDirectory2();
00965     break;
00966 
00967   case SYNC_STATE_DELETE_SUBFOLDERS:
00968     mSyncState = SYNC_STATE_LIST_MESSAGES;
00969     if( !foldersForDeletionOnServer.isEmpty() ) {
00970       newState( mProgress, i18n("Deleting folders from server"));
00971       CachedImapJob* job = new CachedImapJob( foldersForDeletionOnServer,
00972                                                   CachedImapJob::tDeleteFolders, this );
00973       connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
00974       connect( job, SIGNAL( finished() ), this, SLOT( slotFolderDeletionOnServerFinished() ) );
00975       job->start();
00976       break;
00977     }
00978     // Not needed, the next step emits newState very quick
00979     //newState( mProgress, i18n("No folders to delete from server"));
00980       // Carry on
00981 
00982   case SYNC_STATE_LIST_MESSAGES:
00983     mSyncState = SYNC_STATE_DELETE_MESSAGES;
00984     if( !noContent() ) {
00985       newState( mProgress, i18n("Retrieving message list"));
00986       listMessages();
00987       break;
00988     }
00989     // Else carry on
00990 
00991   case SYNC_STATE_DELETE_MESSAGES:
00992     mSyncState = SYNC_STATE_EXPUNGE_MESSAGES;
00993     if( !noContent() ) {
00994       if( deleteMessages() ) {
00995         // Fine, we will continue with the next state
00996       } else {
00997         // No messages to delete, skip to GET_MESSAGES
00998         newState( mProgress, i18n("No messages to delete..."));
00999         mSyncState = SYNC_STATE_GET_MESSAGES;
01000         serverSyncInternal();
01001       }
01002       break;
01003     }
01004     // Else carry on
01005 
01006   case SYNC_STATE_EXPUNGE_MESSAGES:
01007     mSyncState = SYNC_STATE_GET_MESSAGES;
01008     if( !noContent() ) {
01009       newState( mProgress, i18n("Expunging deleted messages"));
01010       CachedImapJob *job = new CachedImapJob( QString::null,
01011                                               CachedImapJob::tExpungeFolder, this );
01012       connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
01013       connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
01014       job->start();
01015       break;
01016     }
01017     // Else carry on
01018 
01019   case SYNC_STATE_GET_MESSAGES:
01020     mSyncState = SYNC_STATE_HANDLE_INBOX;
01021     if( !noContent() ) {
01022       if( !mMsgsForDownload.isEmpty() ) {
01023         newState( mProgress, i18n("Retrieving new messages"));
01024         CachedImapJob *job = new CachedImapJob( mMsgsForDownload,
01025                                                 CachedImapJob::tGetMessage,
01026                                                 this );
01027         connect( job, SIGNAL( progress(unsigned long, unsigned long) ),
01028                  this, SLOT( slotProgress(unsigned long, unsigned long) ) );
01029         connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) );
01030         connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
01031         job->start();
01032         mMsgsForDownload.clear();
01033         break;
01034       } else {
01035         newState( mProgress, i18n("No new messages from server"));
01036         /* There were no messages to download, but it could be that we uploaded some
01037            which we didn't need to download again because we already knew the uid.
01038            Now that we are sure there is nothing to download, and everything that had
01039            to be deleted on the server has been deleted, adjust our local notion of the
01040            highes uid seen thus far. */
01041         slotUpdateLastUid();
01042         if( mLastUid == 0 && uidWriteTimer == -1 ) {
01043           // This is probably a new and empty folder. Write the UID cache
01044           if ( writeUidCache() == -1 ) {
01045             resetSyncState();
01046             emit folderComplete( this, false );
01047             return;
01048           }
01049         }
01050       }
01051     }
01052 
01053     // Else carry on
01054 
01055   case SYNC_STATE_HANDLE_INBOX:
01056     // Wrap up the 'download emails' stage. We always end up at 95 here.
01057     mProgress = 95;
01058     mSyncState = SYNC_STATE_TEST_ANNOTATIONS;
01059 
01060   #define KOLAB_FOLDERTEST "/vendor/kolab/folder-test"
01061   case SYNC_STATE_TEST_ANNOTATIONS:
01062     mSyncState = SYNC_STATE_GET_ANNOTATIONS;
01063     // The first folder with user rights to write annotations
01064     if( !mAccount->annotationCheckPassed() &&
01065          ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) )
01066          && !imapPath().isEmpty() && imapPath() != "/" ) {
01067       kdDebug(5006) << "Setting test attribute on folder: "<< folder()->prettyURL() << endl;
01068       newState( mProgress, i18n("Checking annotation support"));
01069 
01070       KURL url = mAccount->getUrl();
01071       url.setPath( imapPath() );
01072       KMail::AnnotationList annotations; // to be set
01073 
01074       KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" );
01075       annotations.append( attr );
01076 
01077       kdDebug(5006) << "Setting test attribute to "<< url << endl;
01078       KIO::Job* job = AnnotationJobs::multiSetAnnotation( mAccount->slave(),
01079           url, annotations );
01080       ImapAccountBase::jobData jd( url.url(), folder() );
01081       jd.cancellable = true; // we can always do so later
01082       mAccount->insertJob(job, jd);
01083        connect(job, SIGNAL(result(KIO::Job *)),
01084               SLOT(slotTestAnnotationResult(KIO::Job *)));
01085       break;
01086     }
01087 
01088   case SYNC_STATE_GET_ANNOTATIONS: {
01089 #define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type"
01090 #define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for"
01091 //#define KOLAB_FOLDERTYPE "/comment"  //for testing, while cyrus-imap doesn't support /vendor/*
01092     mSyncState = SYNC_STATE_SET_ANNOTATIONS;
01093 
01094     bool needToGetInitialAnnotations = false;
01095     if ( !noContent() ) {
01096       // for a folder we didn't create ourselves: get annotation from server
01097       if ( mAnnotationFolderType == "FROMSERVER" ) {
01098         needToGetInitialAnnotations = true;
01099         mAnnotationFolderType = QString::null;
01100       } else {
01101         updateAnnotationFolderType();
01102       }
01103     }
01104 
01105     // First retrieve the annotation, so that we know we have to set it if it's not set.
01106     // On the other hand, if the user changed the contentstype, there's no need to get first.
01107     if ( !noContent() && mAccount->hasAnnotationSupport() &&
01108         ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) {
01109       QStringList annotations; // list of annotations to be fetched
01110       if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() )
01111         annotations << KOLAB_FOLDERTYPE;
01112       if ( !mIncidencesForChanged )
01113         annotations << KOLAB_INCIDENCESFOR;
01114       if ( !annotations.isEmpty() ) {
01115         newState( mProgress, i18n("Retrieving annotations"));
01116         KURL url = mAccount->getUrl();
01117         url.setPath( imapPath() );
01118         AnnotationJobs::MultiGetAnnotationJob* job =
01119           AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations );
01120         ImapAccountBase::jobData jd( url.url(), folder() );
01121         jd.cancellable = true;
01122         mAccount->insertJob(job, jd);
01123 
01124         connect( job, SIGNAL(annotationResult(const QString&, const QString&, bool)),
01125                  SLOT(slotAnnotationResult(const QString&, const QString&, bool)) );
01126         connect( job, SIGNAL(result(KIO::Job *)),
01127                  SLOT(slotGetAnnotationResult(KIO::Job *)) );
01128         break;
01129       }
01130     }
01131   } // case
01132   case SYNC_STATE_SET_ANNOTATIONS:
01133 
01134     mSyncState = SYNC_STATE_SET_ACLS;
01135     if ( !noContent() && mAccount->hasAnnotationSupport() &&
01136          ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
01137       newState( mProgress, i18n("Setting annotations"));
01138       KURL url = mAccount->getUrl();
01139       url.setPath( imapPath() );
01140       KMail::AnnotationList annotations; // to be set
01141       if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) {
01142         KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared", mAnnotationFolderType );
01143         annotations.append( attr );
01144         kdDebug(5006) << "Setting folder-type annotation for " << label() << " to " << mAnnotationFolderType << endl;
01145       }
01146       if ( mIncidencesForChanged ) {
01147         const QString val = incidencesForToString( mIncidencesFor );
01148         KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared", val );
01149         annotations.append( attr );
01150         kdDebug(5006) << "Setting incidences-for annotation for " << label() << " to " << val << endl;
01151       }
01152       if ( !annotations.isEmpty() ) {
01153         KIO::Job* job =
01154           AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations );
01155         ImapAccountBase::jobData jd( url.url(), folder() );
01156         jd.cancellable = true; // we can always do so later
01157         mAccount->insertJob(job, jd);
01158 
01159         connect(job, SIGNAL(annotationChanged( const QString&, const QString&, const QString& ) ),
01160                 SLOT( slotAnnotationChanged( const QString&, const QString&, const QString& ) ));
01161         connect(job, SIGNAL(result(KIO::Job *)),
01162                 SLOT(slotSetAnnotationResult(KIO::Job *)));
01163         break;
01164       }
01165     }
01166 
01167   case SYNC_STATE_SET_ACLS:
01168     mSyncState = SYNC_STATE_GET_ACLS;
01169 
01170     if( !noContent() && mAccount->hasACLSupport() &&
01171       ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
01172       bool hasChangedACLs = false;
01173       ACLList::ConstIterator it = mACLList.begin();
01174       for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) {
01175         hasChangedACLs = (*it).changed;
01176       }
01177       if ( hasChangedACLs ) {
01178         newState( mProgress, i18n("Setting permissions"));
01179         KURL url = mAccount->getUrl();
01180         url.setPath( imapPath() );
01181         KIO::Job* job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url, mACLList );
01182         ImapAccountBase::jobData jd( url.url(), folder() );
01183         mAccount->insertJob(job, jd);
01184 
01185         connect(job, SIGNAL(result(KIO::Job *)),
01186                 SLOT(slotMultiSetACLResult(KIO::Job *)));
01187         connect(job, SIGNAL(aclChanged( const QString&, int )),
01188                 SLOT(slotACLChanged( const QString&, int )) );
01189         break;
01190       }
01191     }
01192 
01193   case SYNC_STATE_GET_ACLS:
01194     mSyncState = SYNC_STATE_GET_QUOTA;
01195 
01196     if( !noContent() && mAccount->hasACLSupport() ) {
01197       newState( mProgress, i18n( "Retrieving permissions" ) );
01198       mAccount->getACL( folder(), mImapPath );
01199       connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
01200                this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
01201       break;
01202     }
01203   case SYNC_STATE_GET_QUOTA:
01204     // Continue with the subfolders
01205     mSyncState = SYNC_STATE_FIND_SUBFOLDERS;
01206     if( !noContent() && mAccount->hasQuotaSupport() ) {
01207       newState( mProgress, i18n("Getting quota information"));
01208       KURL url = mAccount->getUrl();
01209       url.setPath( imapPath() );
01210       KIO::Job* job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url );
01211       ImapAccountBase::jobData jd( url.url(), folder() );
01212       mAccount->insertJob(job, jd);
01213       connect( job, SIGNAL( storageQuotaResult( const QuotaInfo& ) ),
01214           SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) );
01215       connect( job, SIGNAL(result(KIO::Job *)),
01216           SLOT(slotQuotaResult(KIO::Job *)) );
01217       break;
01218     }
01219   case SYNC_STATE_FIND_SUBFOLDERS:
01220     {
01221       mProgress = 98;
01222       newState( mProgress, i18n("Updating cache file"));
01223 
01224       mSyncState = SYNC_STATE_SYNC_SUBFOLDERS;
01225       mSubfoldersForSync.clear();
01226       mCurrentSubfolder = 0;
01227       if( folder() && folder()->child() ) {
01228         KMFolderNode *node = folder()->child()->first();
01229         while( node ) {
01230           if( !node->isDir() ) {
01231             KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
01232             // Only sync folders that have been accepted by the server
01233             if ( !storage->imapPath().isEmpty()
01234                  // and that were not just deleted from it
01235                  && !foldersForDeletionOnServer.contains( storage->imapPath() ) ) {
01236               mSubfoldersForSync << storage;
01237             } else {
01238               kdDebug(5006) << "Do not add " << storage->label()
01239                 << " to synclist" << endl;
01240             }
01241           }
01242           node = folder()->child()->next();
01243         }
01244       }
01245 
01246     // All done for this folder.
01247     mProgress = 100; // all done
01248     newState( mProgress, i18n("Synchronization done"));
01249       KURL url = mAccount->getUrl();
01250       url.setPath( imapPath() );
01251       kmkernel->iCalIface().folderSynced( folder(), url );
01252     }
01253 
01254     if ( !mRecurse ) // "check mail for this folder" only
01255       mSubfoldersForSync.clear();
01256 
01257     // Carry on
01258   case SYNC_STATE_SYNC_SUBFOLDERS:
01259     {
01260       if( mCurrentSubfolder ) {
01261         disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
01262                     this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
01263         mCurrentSubfolder = 0;
01264       }
01265 
01266       if( mSubfoldersForSync.isEmpty() ) {
01267         mSyncState = SYNC_STATE_INITIAL;
01268         mAccount->addUnreadMsgCount( this, countUnread() ); // before closing
01269         close("cachedimap");
01270         emit folderComplete( this, true );
01271       } else {
01272         mCurrentSubfolder = mSubfoldersForSync.front();
01273         mSubfoldersForSync.pop_front();
01274         connect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
01275                  this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
01276 
01277         //kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl;
01278         assert( !mCurrentSubfolder->imapPath().isEmpty() );
01279         mCurrentSubfolder->setAccount( account() );
01280         bool recurse = mCurrentSubfolder->noChildren() ? false : true;
01281         mCurrentSubfolder->serverSync( recurse );
01282       }
01283     }
01284     break;
01285 
01286   default:
01287     kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state "
01288               << mSyncState << endl;
01289   }
01290 }
01291 
01292 /* Connected to the imap account's connectionResult signal.
01293    Emitted when the slave connected or failed to connect.
01294 */
01295 void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString& errorMsg )
01296 {
01297   disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
01298               this, SLOT( slotConnectionResult(int, const QString&) ) );
01299   if ( !errorCode ) {
01300     // Success
01301     mSyncState = SYNC_STATE_GET_USERRIGHTS;
01302     mProgress += 5;
01303     serverSyncInternal();
01304   } else {
01305     // Error (error message already shown by the account)
01306     newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ));
01307     emit folderComplete(this, false);
01308   }
01309 }
01310 
01311 /* find new messages (messages without a UID) */
01312 QValueList<unsigned long> KMFolderCachedImap::findNewMessages()
01313 {
01314   QValueList<unsigned long> result;
01315   for( int i = 0; i < count(); ++i ) {
01316     KMMsgBase *msg = getMsgBase( i );
01317     if( !msg ) continue; /* what goes on if getMsg() returns 0? */
01318     if ( msg->UID() == 0 )
01319       result.append( msg->getMsgSerNum() );
01320   }
01321   return result;
01322 }
01323 
01324 /* Upload new messages to server */
01325 void KMFolderCachedImap::uploadNewMessages()
01326 {
01327   QValueList<unsigned long> newMsgs = findNewMessages();
01328   if( !newMsgs.isEmpty() ) {
01329     if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) {
01330       newState( mProgress, i18n("Uploading messages to server"));
01331       CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this );
01332       connect( job, SIGNAL( progress( unsigned long, unsigned long) ),
01333                this, SLOT( slotPutProgress(unsigned long, unsigned long) ) );
01334       connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
01335       job->start();
01336       return;
01337     } else {
01338       KMCommand *command = rescueUnsyncedMessages();
01339       connect( command, SIGNAL( completed( KMCommand * ) ),
01340                this, SLOT( serverSyncInternal() ) );
01341     }
01342   } else { // nothing to upload
01343     if ( mUserRights != mOldUserRights && (mOldUserRights & KMail::ACLJobs::Insert)
01344          && !(mUserRights & KMail::ACLJobs::Insert) ) {
01345       // write access revoked
01346       KMessageBox::information( 0, i18n("<p>Your access rights to folder <b>%1</b> have been restricted, "
01347           "it will no longer be possible to add messages to this folder.</p>").arg( folder()->prettyURL() ),
01348           i18n("Acces rights revoked"), "KMailACLRevocationNotification" );
01349     }
01350   }
01351   newState( mProgress, i18n("No messages to upload to server"));
01352   serverSyncInternal();
01353 }
01354 
01355 /* Progress info during uploadNewMessages */
01356 void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total )
01357 {
01358   // (going from mProgress to mProgress+10)
01359   int progressSpan = 10;
01360   newState( mProgress + (progressSpan * done) / total, QString::null );
01361   if ( done == total ) // we're done
01362     mProgress += progressSpan;
01363 }
01364 
01365 /* Upload message flags to server */
01366 void KMFolderCachedImap::uploadFlags()
01367 {
01368   if ( !uidMap.isEmpty() ) {
01369     mStatusFlagsJobs = 0;
01370     newState( mProgress, i18n("Uploading status of messages to server"));
01371 
01372     // FIXME DUPLICATED FROM KMFOLDERIMAP
01373     QMap< QString, QStringList > groups;
01374     //open(); //already done
01375     for( int i = 0; i < count(); ++i ) {
01376       KMMsgBase* msg = getMsgBase( i );
01377       if( !msg || msg->UID() == 0 )
01378         // Either not a valid message or not one that is on the server yet
01379         continue;
01380 
01381       QString flags = KMFolderImap::statusToFlags(msg->status(), mPermanentFlags);
01382       // Collect uids for each typem of flags.
01383       QString uid;
01384       uid.setNum( msg->UID() );
01385       groups[flags].append(uid);
01386     }
01387     QMapIterator< QString, QStringList > dit;
01388     for( dit = groups.begin(); dit != groups.end(); ++dit ) {
01389       QCString flags = dit.key().latin1();
01390       QStringList sets = KMFolderImap::makeSets( (*dit), true );
01391       mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap....
01392       // Send off a status setting job for each set.
01393       for( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
01394         QString imappath = imapPath() + ";UID=" + ( *slit );
01395         mAccount->setImapStatus(folder(), imappath, flags);
01396       }
01397     }
01398     // FIXME END DUPLICATED FROM KMFOLDERIMAP
01399 
01400     if ( mStatusFlagsJobs ) {
01401       connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
01402                this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
01403       return;
01404     }
01405   }
01406   newState( mProgress, i18n("No messages to upload to server"));
01407   serverSyncInternal();
01408 }
01409 
01410 void KMFolderCachedImap::uploadSeenFlags()
01411 {
01412   if ( !uidMap.isEmpty() ) {
01413     mStatusFlagsJobs = 0;
01414     newState( mProgress, i18n("Uploading status of messages to server"));
01415 
01416     QValueList<ulong> seenUids, unseenUids;
01417     for( int i = 0; i < count(); ++i ) {
01418       KMMsgBase* msg = getMsgBase( i );
01419       if( !msg || msg->UID() == 0 )
01420         // Either not a valid message or not one that is on the server yet
01421         continue;
01422 
01423       if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead )
01424         seenUids.append( msg->UID() );
01425       else
01426         unseenUids.append( msg->UID() );
01427     }
01428     if ( !seenUids.isEmpty() ) {
01429       QStringList sets = KMFolderImap::makeSets( seenUids, true );
01430       mStatusFlagsJobs += sets.count();
01431       for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
01432         QString imappath = imapPath() + ";UID=" + ( *it );
01433         mAccount->setImapSeenStatus( folder(), imappath, true );
01434       }
01435     }
01436     if ( !unseenUids.isEmpty() ) {
01437       QStringList sets = KMFolderImap::makeSets( unseenUids, true );
01438       mStatusFlagsJobs += sets.count();
01439       for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
01440         QString imappath = imapPath() + ";UID=" + ( *it );
01441         mAccount->setImapSeenStatus( folder(), imappath, false );
01442       }
01443     }
01444 
01445     if ( mStatusFlagsJobs ) {
01446       connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
01447                this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
01448       return;
01449     }
01450   }
01451   newState( mProgress, i18n("No messages to upload to server"));
01452   serverSyncInternal();
01453 }
01454 
01455 void KMFolderCachedImap::slotImapStatusChanged(KMFolder* folder, const QString&, bool cont)
01456 {
01457   if ( mSyncState == SYNC_STATE_INITIAL ){
01458       //kdDebug(5006) << "IMAP status changed but reset " << endl;
01459       return; // we were reset
01460   }
01461   //kdDebug(5006) << "IMAP status changed for folder: " << folder->prettyURL() << endl;
01462   if ( folder->storage() == this ) {
01463     --mStatusFlagsJobs;
01464     if ( mStatusFlagsJobs == 0 || !cont ) // done or aborting
01465       disconnect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
01466                   this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
01467     if ( mStatusFlagsJobs == 0 && cont ) {
01468       mProgress += 5;
01469       serverSyncInternal();
01470       //kdDebug(5006) << "Proceeding with mailcheck." << endl;
01471     }
01472   }
01473 }
01474 
01475 // This is not perfect, what if the status didn't really change? Oh well ...
01476 void KMFolderCachedImap::setStatus( int idx, KMMsgStatus status, bool toggle)
01477 {
01478   KMFolderMaildir::setStatus( idx, status, toggle );
01479   mStatusChangedLocally = true;
01480 }
01481 
01482 void KMFolderCachedImap::setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle)
01483 {
01484   KMFolderMaildir::setStatus(ids, status, toggle);
01485   mStatusChangedLocally = true;
01486 }
01487 
01488 /* Upload new folders to server */
01489 void KMFolderCachedImap::createNewFolders()
01490 {
01491   QValueList<KMFolderCachedImap*> newFolders = findNewFolders();
01492   //kdDebug(5006) << label() << " createNewFolders:" << newFolders.count() << " new folders." << endl;
01493   if( !newFolders.isEmpty() ) {
01494     newState( mProgress, i18n("Creating subfolders on server"));
01495     CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this );
01496     connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
01497     connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
01498     job->start();
01499   } else {
01500     serverSyncInternal();
01501   }
01502 }
01503 
01504 QValueList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders()
01505 {
01506   QValueList<KMFolderCachedImap*> newFolders;
01507   if( folder() && folder()->child() ) {
01508     KMFolderNode *node = folder()->child()->first();
01509     while( node ) {
01510       if( !node->isDir() ) {
01511         if( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) {
01512           kdError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! "
01513                         << node->name() << " is not an IMAP folder\n";
01514           node = folder()->child()->next();
01515           assert(0);
01516         }
01517         KMFolderCachedImap* folder = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
01518         if( folder->imapPath().isEmpty() ) {
01519           newFolders << folder;
01520         }
01521       }
01522       node = folder()->child()->next();
01523     }
01524   }
01525   return newFolders;
01526 }
01527 
01528 bool KMFolderCachedImap::deleteMessages()
01529 {
01530   /* Delete messages from cache that are gone from the server */
01531   QPtrList<KMMessage> msgsForDeletion;
01532 
01533   // It is not possible to just go over all indices and remove
01534   // them one by one because the index list can get resized under
01535   // us. So use msg pointers instead
01536 
01537   QStringList uids;
01538   QMap<ulong,int>::const_iterator it = uidMap.constBegin();
01539   for( ; it != uidMap.end(); it++ ) {
01540     ulong uid ( it.key() );
01541     if( uid!=0 && !uidsOnServer.find( uid ) ) {
01542       uids << QString::number( uid );
01543       msgsForDeletion.append( getMsg( *it ) );
01544     }
01545   }
01546 
01547   if( !msgsForDeletion.isEmpty() ) {
01548 #if MAIL_LOSS_DEBUGGING
01549       if ( KMessageBox::warningYesNo(
01550              0, i18n( "<qt><p>Mails on the server in folder <b>%1</b> were deleted. "
01551                  "Do you want to delete them locally?<br>UIDs: %2</p></qt>" )
01552              .arg( folder()->prettyURL() ).arg( uids.join(",") ) ) == KMessageBox::Yes )
01553 #endif
01554         removeMsg( msgsForDeletion );
01555   }
01556 
01557   if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) )
01558     return false;
01559 
01560   /* Delete messages from the server that we dont have anymore */
01561   if( !uidsForDeletionOnServer.isEmpty() ) {
01562     newState( mProgress, i18n("Deleting removed messages from server"));
01563     QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true );
01564     uidsForDeletionOnServer.clear();
01565     kdDebug(5006) << "Deleting " << sets.count() << " sets of messages from server folder " << imapPath() << endl;
01566     CachedImapJob *job = new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this );
01567     connect( job, SIGNAL( result(KMail::FolderJob *) ),
01568              this, SLOT( slotDeleteMessagesResult(KMail::FolderJob *) ) );
01569     job->start();
01570     return true;
01571   } else {
01572 
01573     // Nothing to delete on the server, make sure the map is clear again.
01574     // Normally this wouldn't be necessary, but there can be stale maps because of
01575     // https://issues.kolab.org/issue3833.
01576     mDeletedUIDsSinceLastSync.clear();
01577     return false;
01578   }
01579 }
01580 
01581 void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob* job )
01582 {
01583   if ( job->error() ) {
01584     // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages
01585     mSyncState = SYNC_STATE_GET_MESSAGES;
01586   } else {
01587     // deleting on the server went fine, clear the pending deletions cache
01588     mDeletedUIDsSinceLastSync.clear();
01589   }
01590   mProgress += 10;
01591   serverSyncInternal();
01592 }
01593 
01594 void KMFolderCachedImap::checkUidValidity() {
01595   // IMAP root folders don't seem to have a UID validity setting.
01596   // Also, don't try the uid validity on new folders
01597   if( imapPath().isEmpty() || imapPath() == "/" )
01598     // Just proceed
01599     serverSyncInternal();
01600   else {
01601     newState( mProgress, i18n("Checking folder validity"));
01602     CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this );
01603     connect( job, SIGNAL(permanentFlags(int)), SLOT(slotPermanentFlags(int)) );
01604     connect( job, SIGNAL( result( KMail::FolderJob* ) ),
01605              this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) );
01606     job->start();
01607   }
01608 }
01609 
01610 void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob* job )
01611 {
01612   if ( job->error() ) { // there was an error and the user chose "continue"
01613     // We can't continue doing anything in the same folder though, it would delete all mails.
01614     // But we can continue to subfolders if any. Well we can also try annotation/acl stuff...
01615     mSyncState = SYNC_STATE_HANDLE_INBOX;
01616   }
01617   mProgress += 5;
01618   serverSyncInternal();
01619 }
01620 
01621 void KMFolderCachedImap::slotPermanentFlags(int flags)
01622 {
01623   mPermanentFlags = flags;
01624 }
01625 
01626 /* This will only list the messages in a folder.
01627    No directory listing done*/
01628 void KMFolderCachedImap::listMessages() {
01629   bool groupwareOnly = GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount()
01630                && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id()
01631                && folder()->isSystemFolder()
01632                && mImapPath == "/INBOX/";
01633   // Don't list messages on the root folder, and skip the inbox, if this is
01634   // the inbox of a groupware-only dimap account
01635   if( imapPath() == "/" || groupwareOnly ) {
01636     serverSyncInternal();
01637     return;
01638   }
01639 
01640   if( !mAccount->slave() ) { // sync aborted
01641     resetSyncState();
01642     emit folderComplete( this, false );
01643     return;
01644   }
01645   uidsOnServer.clear();
01646   uidsOnServer.resize( count() * 2 );
01647   uidsForDeletionOnServer.clear();
01648   mMsgsForDownload.clear();
01649   mUidsForDownload.clear();
01650   // listing is only considered successful if saw a syntactically correct imapdigest
01651   mFoundAnIMAPDigest = false;
01652 
01653   CachedImapJob* job = new CachedImapJob( FolderJob::tListMessages, this );
01654   connect( job, SIGNAL( result(KMail::FolderJob *) ),
01655            this, SLOT( slotGetLastMessagesResult(KMail::FolderJob *) ) );
01656   job->start();
01657 }
01658 
01659 void KMFolderCachedImap::slotGetLastMessagesResult(KMail::FolderJob *job)
01660 {
01661   getMessagesResult(job, true);
01662 }
01663 
01664 // Connected to the listMessages job in CachedImapJob
01665 void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const QByteArray & data)
01666 {
01667   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
01668   if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
01669     kdDebug(5006) << "could not find job!?!?!" << endl;
01670     // be sure to reset the sync state, if the listing was partial we would
01671     // otherwise delete not-listed mail locally, and on the next sync on the server
01672     // as well
01673     mSyncState = SYNC_STATE_HANDLE_INBOX;
01674     serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */
01675     return;
01676   }
01677   (*it).cdata += QCString(data, data.size() + 1);
01678   int pos = (*it).cdata.find("\r\n--IMAPDIGEST");
01679   if (pos > 0) {
01680     int a = (*it).cdata.find("\r\nX-uidValidity:");
01681     if (a != -1) {
01682       int b = (*it).cdata.find("\r\n", a + 17);
01683       setUidValidity((*it).cdata.mid(a + 17, b - a - 17));
01684     }
01685     a = (*it).cdata.find("\r\nX-Access:");
01686     // Only trust X-Access (i.e. the imap select info) if we don't know mUserRights.
01687     // The latter is more accurate (checked on every sync) whereas X-Access is only
01688     // updated when selecting the folder again, which might not happen if using
01689     // RMB / Check Mail in this folder. We don't need two (potentially conflicting)
01690     // sources for the readonly setting, in any case.
01691     if (a != -1 && mUserRights == -1 ) {
01692       int b = (*it).cdata.find("\r\n", a + 12);
01693       const QString access = (*it).cdata.mid(a + 12, b - a - 12);
01694       setReadOnly( access == "Read only" );
01695     }
01696     (*it).cdata.remove(0, pos);
01697     mFoundAnIMAPDigest = true;
01698   }
01699   pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
01700   // Start with something largish when rebuilding the cache
01701   if ( uidsOnServer.size() == 0 )
01702     uidsOnServer.resize( KMail::nextPrime( 2000 ) );
01703   const int v = 42;
01704   while (pos >= 0) {
01705       /*
01706     KMMessage msg;
01707     msg.fromString((*it).cdata.mid(16, pos - 16));
01708     const int flags = msg.headerField("X-Flags").toInt();
01709     const ulong size = msg.headerField("X-Length").toULong();
01710     const ulong uid = msg.UID();
01711        */
01712     // The below is optimized for speed, not prettiness. The commented out chunk
01713     // above was the solution copied from kmfolderimap, and it's 15-20% slower.
01714     const QCString& entry( (*it).cdata );
01715     const int indexOfUID = entry.find("X-UID", 16);
01716     const int startOfUIDValue = indexOfUID  + 7;
01717     const int indexOfLength = entry.find("X-Length", startOfUIDValue ); // we know length comes after UID
01718     const int startOfLengthValue = indexOfLength + 10;
01719     const int indexOfFlags = entry.find("X-Flags", startOfLengthValue ); // we know flags comes last
01720     const int startOfFlagsValue = indexOfFlags + 9;
01721 
01722     const int flags = entry.mid( startOfFlagsValue, entry.find( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt();
01723     const ulong size = entry.mid( startOfLengthValue, entry.find( '\r', startOfLengthValue ) - startOfLengthValue ).toULong();
01724     const ulong uid = entry.mid( startOfUIDValue, entry.find( '\r', startOfUIDValue ) - startOfUIDValue ).toULong();
01725 
01726     const bool deleted = ( flags & 8 );
01727     if ( !deleted ) {
01728       if( uid != 0 ) {
01729         if ( uidsOnServer.count() == uidsOnServer.size() ) {
01730           uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) );
01731           //kdDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl;
01732         }
01733         uidsOnServer.insert( uid, &v );
01734       }
01735       bool redownload = false;
01736       if (  uid <= lastUid() ) {
01737        /*
01738         * If this message UID is not present locally, then it must
01739         * have been deleted by the user, so we delete it on the
01740         * server also. If we don't have delete permissions on the server,
01741         * re-download the message, it must have vanished by some error, or
01742         * while we still thought we were allowed to delete (ACL change).
01743         *
01744         * This relies heavily on lastUid() being correct at all times.
01745         */
01746         // kdDebug(5006) << "KMFolderCachedImap::slotGetMessagesData() : folder "<<label()<<" already has msg="<<msg->headerField("Subject") << ", UID="<<uid << ", lastUid = " << mLastUid << endl;
01747         KMMsgBase *existingMessage = findByUID(uid);
01748         if( !existingMessage ) {
01749 #if MAIL_LOSS_DEBUGGING
01750            kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should delete it" << endl;
01751 #endif
01752           // double check we deleted it since the last sync
01753            if ( mDeletedUIDsSinceLastSync.contains(uid) ) {
01754                if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::Delete ) ) {
01755 #if MAIL_LOSS_DEBUGGING
01756                    kdDebug(5006) << "message with uid " << uid << " is gone from local cache. Must be deleted on server!!!" << endl;
01757 #endif
01758                    uidsForDeletionOnServer << uid;
01759                } else {
01760                    redownload = true;
01761                }
01762            } else {
01763                kdDebug(5006) << "WARNING: ####### " << endl;
01764                kdDebug(5006) << "Message locally missing but not deleted in folder: " << folder()->prettyURL() << endl;
01765                kdDebug(5006) << "The missing UID: " << uid << ". It will be redownloaded " << endl;
01766                redownload = true;
01767            }
01768 
01769         } else {
01770           // if this is a read only folder, ignore status updates from the server
01771           // since we can't write our status back our local version is what has to
01772           // be considered correct.
01773           if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) {
01774             /* The message is OK, update flags */
01775             KMFolderImap::flagsToStatus( existingMessage, flags,  false, mReadOnly ? INT_MAX : mPermanentFlags );
01776           } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
01777             KMFolderImap::seenFlagToStatus( existingMessage, flags );
01778           }
01779         }
01780         // kdDebug(5006) << "message with uid " << uid << " found in the local cache. " << endl;
01781       }
01782       if ( uid > lastUid() || redownload ) {
01783 #if MAIL_LOSS_DEBUGGING
01784         kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should download it" << endl;
01785 #endif
01786         // The message is new since the last sync, but we might have just uploaded it, in which case
01787         // the uid map already contains it.
01788         if ( !uidMap.contains( uid ) ) {
01789           mMsgsForDownload << KMail::CachedImapJob::MsgForDownload(uid, flags, size);
01790           if( imapPath() == "/INBOX/" )
01791             mUidsForDownload << uid;
01792         }
01793         // Remember the highest uid and once the download is completed, update mLastUid
01794         if ( uid > mTentativeHighestUid ) {
01795 #if MAIL_LOSS_DEBUGGING
01796           kdDebug(5006) << "Setting the tentative highest UID to: " << uid << endl;
01797 #endif
01798           mTentativeHighestUid = uid;
01799         }
01800       }
01801     }
01802     (*it).cdata.remove(0, pos);
01803     (*it).done++;
01804     pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
01805   }
01806 }
01807 
01808 void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet )
01809 {
01810   mProgress += 10;
01811   if ( !job->error() && !mFoundAnIMAPDigest ) {
01812       kdWarning(5006) << "######## Folderlisting did not complete, but there was no error! "
01813           "Aborting sync of folder: " << folder()->prettyURL() << endl;
01814 #if MAIL_LOSS_DEBUGGING
01815       kmkernel->emergencyExit( i18n("Folder listing failed in interesting ways." ) );
01816 #endif
01817   }
01818   if( job->error() ) { // error listing messages but the user chose to continue
01819     mContentState = imapNoInformation;
01820     mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder
01821   } else {
01822     if( lastSet ) { // always true here (this comes from online-imap...)
01823       mContentState = imapFinished;
01824       mStatusChangedLocally = false; // we are up to date again
01825     }
01826   }
01827   serverSyncInternal();
01828 }
01829 
01830 void KMFolderCachedImap::slotProgress(unsigned long done, unsigned long total)
01831 {
01832   int progressSpan = 100 - 5 - mProgress;
01833   //kdDebug(5006) << "KMFolderCachedImap::slotProgress done=" << done << " total=" << total << "=> mProgress=" << mProgress + ( progressSpan * done ) / total << endl;
01834   // Progress info while retrieving new emails
01835   // (going from mProgress to mProgress+progressSpan)
01836   newState( mProgress + (progressSpan * done) / total, QString::null );
01837 }
01838 
01839 void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount)
01840 {
01841   assert( aAccount->isA("KMAcctCachedImap") );
01842   mAccount = aAccount;
01843   if( imapPath()=="/" ) aAccount->setFolder( folder() );
01844 
01845   // Folder was renamed in a previous session, and the user didn't sync yet
01846   QString newName = mAccount->renamedFolder( imapPath() );
01847   if ( !newName.isEmpty() )
01848     folder()->setLabel( newName );
01849 
01850   if( !folder() || !folder()->child() || !folder()->child()->count() ) return;
01851   for( KMFolderNode* node = folder()->child()->first(); node;
01852        node = folder()->child()->next() )
01853     if (!node->isDir())
01854       static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage())->setAccount(aAccount);
01855 }
01856 
01857 void KMFolderCachedImap::listNamespaces()
01858 {
01859   ImapAccountBase::ListType type = ImapAccountBase::List;
01860   if ( mAccount->onlySubscribedFolders() )
01861     type = ImapAccountBase::ListSubscribed;
01862 
01863   kdDebug(5006) << "listNamespaces " << mNamespacesToList << endl;
01864   if ( mNamespacesToList.isEmpty() ) {
01865     mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
01866     mPersonalNamespacesCheckDone = true;
01867 
01868     QStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS];
01869     ns += mAccount->namespaces()[ImapAccountBase::SharedNS];
01870     mNamespacesToCheck = ns.count();
01871     for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it )
01872     {
01873       if ( (*it).isEmpty() ) {
01874         // ignore empty listings as they have been listed before
01875         --mNamespacesToCheck;
01876         continue;
01877       }
01878       KMail::ListJob* job = new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) );
01879       job->setHonorLocalSubscription( true );
01880       connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
01881               const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
01882           this, SLOT(slotCheckNamespace(const QStringList&, const QStringList&,
01883               const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
01884       job->start();
01885     }
01886     if ( mNamespacesToCheck == 0 ) {
01887       serverSyncInternal();
01888     }
01889     return;
01890   }
01891   mPersonalNamespacesCheckDone = false;
01892 
01893   QString ns = mNamespacesToList.front();
01894   mNamespacesToList.pop_front();
01895 
01896   mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
01897   newState( mProgress, i18n("Retrieving folders for namespace %1").arg(ns));
01898   KMail::ListJob* job = new KMail::ListJob( mAccount, type, this,
01899       mAccount->addPathToNamespace( ns ) );
01900   job->setNamespace( ns );
01901   job->setHonorLocalSubscription( true );
01902   connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
01903           const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
01904       this, SLOT(slotListResult(const QStringList&, const QStringList&,
01905           const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
01906   job->start();
01907 }
01908 
01909 void KMFolderCachedImap::slotCheckNamespace( const QStringList& subfolderNames,
01910                                              const QStringList& subfolderPaths,
01911                                              const QStringList& subfolderMimeTypes,
01912                                              const QStringList& subfolderAttributes,
01913                                              const ImapAccountBase::jobData& jobData )
01914 {
01915   Q_UNUSED( subfolderPaths );
01916   Q_UNUSED( subfolderMimeTypes );
01917   Q_UNUSED( subfolderAttributes );
01918   --mNamespacesToCheck;
01919   kdDebug(5006) << "slotCheckNamespace " << subfolderNames << ",remain=" <<
01920    mNamespacesToCheck << endl;
01921 
01922   // get a correct foldername:
01923   // strip / and make sure it does not contain the delimiter
01924   QString name = jobData.path.mid( 1, jobData.path.length()-2 );
01925   name.remove( mAccount->delimiterForNamespace( name ) );
01926   if ( name.isEmpty() ) {
01927     // should not happen
01928     kdWarning(5006) << "slotCheckNamespace: ignoring empty folder!" << endl;
01929     return;
01930   }
01931 
01932   folder()->createChildFolder();
01933   KMFolderNode *node = 0;
01934   for ( node = folder()->child()->first(); node;
01935         node = folder()->child()->next())
01936   {
01937     if ( !node->isDir() && node->name() == name )
01938       break;
01939   }
01940   if ( !subfolderNames.isEmpty() ) {
01941     if ( node ) {
01942       // folder exists so we have nothing to do - it will be listed later
01943       kdDebug(5006) << "found namespace folder " << name << endl;
01944     } else
01945     {
01946       // create folder
01947       kdDebug(5006) << "create namespace folder " << name << endl;
01948       KMFolder* newFolder = folder()->child()->createFolder( name, false,
01949           KMFolderTypeCachedImap );
01950       if ( newFolder ) {
01951         KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
01952         f->setImapPath( mAccount->addPathToNamespace( name ) );
01953         f->setNoContent( true );
01954         f->setAccount( mAccount );
01955         f->close("cachedimap");
01956         kmkernel->dimapFolderMgr()->contentsChanged();
01957       }
01958     }
01959   } else {
01960     if ( node ) {
01961       kdDebug(5006) << "delete namespace folder " << name << endl;
01962       KMFolder* fld = static_cast<KMFolder*>(node);
01963       kmkernel->dimapFolderMgr()->remove( fld );
01964     }
01965   }
01966 
01967   if ( mNamespacesToCheck == 0 ) {
01968     // all namespaces are done so continue with the next step
01969     serverSyncInternal();
01970   }
01971 }
01972 
01973 // This lists the subfolders on the server
01974 // and (in slotListResult) takes care of folders that have been removed on the server
01975 bool KMFolderCachedImap::listDirectory()
01976 {
01977   if( !mAccount->slave() ) { // sync aborted
01978     resetSyncState();
01979     emit folderComplete( this, false );
01980     return false;
01981   }
01982   mSubfolderState = imapInProgress;
01983 
01984   // get the folders
01985   ImapAccountBase::ListType type = ImapAccountBase::List;
01986   if ( mAccount->onlySubscribedFolders() )
01987     type = ImapAccountBase::ListSubscribed;
01988   KMail::ListJob* job = new KMail::ListJob( mAccount, type, this );
01989   job->setHonorLocalSubscription( true );
01990   connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
01991           const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
01992       this, SLOT(slotListResult(const QStringList&, const QStringList&,
01993           const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
01994   job->start();
01995 
01996   return true;
01997 }
01998 
01999 void KMFolderCachedImap::slotListResult( const QStringList& folderNames,
02000                                          const QStringList& folderPaths,
02001                                          const QStringList& folderMimeTypes,
02002                                          const QStringList& folderAttributes,
02003                                          const ImapAccountBase::jobData& jobData )
02004 {
02005   Q_UNUSED( jobData );
02006   //kdDebug(5006) << label() << ": folderNames=" << folderNames << " folderPaths="
02007   //<< folderPaths << " mimeTypes=" << folderMimeTypes << endl;
02008   mSubfolderNames = folderNames;
02009   mSubfolderPaths = folderPaths;
02010   mSubfolderMimeTypes = folderMimeTypes;
02011   mSubfolderState = imapFinished;
02012   mSubfolderAttributes = folderAttributes;
02013   kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl;
02014 
02015   folder()->createChildFolder();
02016   KMFolderNode *node = folder()->child()->first();
02017   bool root = ( this == mAccount->rootFolder() );
02018 
02019   QPtrList<KMFolder> toRemove;
02020   bool emptyList = ( root && mSubfolderNames.empty() );
02021   if ( !emptyList ) {
02022     while (node) {
02023       if (!node->isDir() ) {
02024         KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
02025 
02026         if ( mSubfolderNames.findIndex(node->name()) == -1 ) {
02027           QString name = node->name();
02028           // as more than one namespace can be listed in the root folder we need to make sure
02029           // that the folder is within the current namespace
02030           bool isInNamespace = ( jobData.curNamespace.isEmpty() ||
02031               jobData.curNamespace == mAccount->namespaceForFolder( f ) );
02032           // ignore some cases
02033           bool ignore = root && ( f->imapPath() == "/INBOX/" ||
02034               mAccount->isNamespaceFolder( name ) || !isInNamespace );
02035 
02036           // This subfolder isn't present on the server
02037           if( !f->imapPath().isEmpty() && !ignore  ) {
02038             // The folder has an imap path set, so it has been
02039             // on the server before. Delete it locally.
02040             toRemove.append( f->folder() );
02041             kdDebug(5006) << node->name() << " isn't on the server. It has an imapPath -> delete it locally" << endl;
02042           }
02043         } else { // folder both local and on server
02044           //kdDebug(5006) << node->name() << " is on the server." << endl;
02045 
02049           int index = mSubfolderNames.findIndex( node->name() );
02050           f->mFolderAttributes = folderAttributes[ index ];
02051         }
02052       } else {
02053         //kdDebug(5006) << "skipping dir node:" << node->name() << endl;
02054       }
02055       node = folder()->child()->next();
02056     }
02057   }
02058 
02059   for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() ) {
02060     rescueUnsyncedMessagesAndDeleteFolder( doomed );
02061   }
02062 
02063   mProgress += 5;
02064 
02065   // just in case there is nothing to rescue
02066   slotRescueDone( 0 );
02067 }
02068 
02069 // This synchronizes the local folders as needed (creation/deletion). No network communication here.
02070 void KMFolderCachedImap::listDirectory2()
02071 {
02072   QString path = folder()->path();
02073   kmkernel->dimapFolderMgr()->quiet(true);
02074 
02075   bool root = ( this == mAccount->rootFolder() );
02076   if ( root && !mAccount->hasInbox() )
02077   {
02078     KMFolderCachedImap *f = 0;
02079     KMFolderNode *node;
02080     // create the INBOX
02081     for (node = folder()->child()->first(); node; node = folder()->child()->next())
02082       if (!node->isDir() && node->name() == "INBOX") break;
02083     if (node) {
02084       f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
02085     } else {
02086       KMFolder* newFolder = folder()->child()->createFolder("INBOX", true, KMFolderTypeCachedImap);
02087       if ( newFolder ) {
02088         f = static_cast<KMFolderCachedImap*>(newFolder->storage());
02089       }
02090     }
02091     if ( f ) {
02092       f->setAccount( mAccount );
02093       f->setImapPath( "/INBOX/" );
02094       f->folder()->setLabel( i18n("inbox") );
02095     }
02096     if (!node) {
02097       if ( f )
02098         f->close("cachedimap");
02099       kmkernel->dimapFolderMgr()->contentsChanged();
02100     }
02101     // so we have an INBOX
02102     mAccount->setHasInbox( true );
02103   }
02104 
02105   if ( root && !mSubfolderNames.isEmpty() ) {
02106     KMFolderCachedImap* parent =
02107       findParent( mSubfolderPaths.first(), mSubfolderNames.first() );
02108     if ( parent ) {
02109       kdDebug(5006) << "KMFolderCachedImap::listDirectory2 - pass listing to "
02110         << parent->label() << endl;
02111       mSubfolderNames.clear();
02112     }
02113   }
02114 
02115   // Find all subfolders present on server but not on disk
02116   QValueVector<int> foldersNewOnServer;
02117   for (uint i = 0; i < mSubfolderNames.count(); i++) {
02118 
02119     // Find the subdir, if already present
02120     KMFolderCachedImap *f = 0;
02121     KMFolderNode *node = 0;
02122     for (node = folder()->child()->first(); node;
02123          node = folder()->child()->next())
02124       if (!node->isDir() && node->name() == mSubfolderNames[i]) break;
02125 
02126     if (!node) {
02127       // This folder is not present here
02128       // Either it's new on the server, or we just deleted it.
02129       QString subfolderPath = mSubfolderPaths[i];
02130       // The code used to look at the uidcache to know if it was "just deleted".
02131       // But this breaks with noContent folders and with shared folders.
02132       // So instead we keep a list in the account.
02133       bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath );
02134       // That list is saved/restored across sessions, but to avoid any mistake,
02135       // ask for confirmation if the folder was deleted in a previous session
02136       // (could be that the folder was deleted & recreated meanwhile from another client...)
02137       if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) {
02138            locallyDeleted = KMessageBox::warningYesNo(
02139              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;
02140       }
02141 
02142       if ( locallyDeleted ) {
02143         kdDebug(5006) << subfolderPath << " was deleted locally => delete on server." << endl;
02144         foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath ); // grab all subsubfolders too
02145       } else {
02146         kdDebug(5006) << subfolderPath << " is a new folder on the server => create local cache" << endl;
02147         foldersNewOnServer.append( i );
02148       }
02149     } else { // Folder found locally
02150       if( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap )
02151         f = dynamic_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
02152       if( f ) {
02153         // kdDebug(5006) << "folder("<<f->name()<<")->imapPath()=" << f->imapPath()
02154         //               << "\nSetting imapPath " << mSubfolderPaths[i] << endl;
02155         // Write folder settings
02156         f->setAccount(mAccount);
02157         f->setNoContent(mSubfolderMimeTypes[i] == "inode/directory");
02158         f->setNoChildren(mSubfolderMimeTypes[i] == "message/digest");
02159         f->setImapPath(mSubfolderPaths[i]);
02160       }
02161     }
02162   }
02163 
02164   /* In case we are ignoring non-groupware folders, and this is the groupware
02165    * main account, find out the contents types of folders that have newly
02166    * appeared on the server. Otherwise just create them and finish listing.
02167    * If a folder is already known to be locally unsubscribed, it won't be
02168    * listed at all, on this level, so these are only folders that we are
02169    * seeing for the first time. */
02170 
02171   /*  Note: We ask the globalsettings, and not the current state of the
02172    *  kmkernel->iCalIface().isEnabled(), since that is false during the
02173    *  very first sync, where we already want to filter. */
02174   if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount()
02175      && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id()
02176      && mAccount->hasAnnotationSupport()
02177      && GlobalSettings::self()->theIMAPResourceEnabled()
02178      && !foldersNewOnServer.isEmpty() ) {
02179 
02180     QStringList paths;
02181     for ( uint i = 0; i < foldersNewOnServer.count(); ++i )
02182       paths << mSubfolderPaths[ foldersNewOnServer[i] ];
02183 
02184     AnnotationJobs::MultiUrlGetAnnotationJob* job =
02185       AnnotationJobs::multiUrlGetAnnotation( mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE );
02186     ImapAccountBase::jobData jd( QString::null, folder() );
02187     jd.cancellable = true;
02188     mAccount->insertJob(job, jd);
02189     connect( job, SIGNAL(result(KIO::Job *)),
02190         SLOT(slotMultiUrlGetAnnotationResult(KIO::Job *)) );
02191 
02192   } else {
02193     createFoldersNewOnServerAndFinishListing( foldersNewOnServer );
02194   }
02195 }
02196 
02197 void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const QValueVector<int> foldersNewOnServer )
02198 {
02199   for ( uint i = 0; i < foldersNewOnServer.count(); ++i ) {
02200     int idx = foldersNewOnServer[i];
02201     KMFolder* newFolder = folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap);
02202     if (newFolder) {
02203       KMFolderCachedImap *f = dynamic_cast<KMFolderCachedImap*>(newFolder->storage());
02204       kdDebug(5006) << " ####### Locally creating folder " << mSubfolderNames[idx] <<endl;
02205       f->close("cachedimap");
02206       f->setAccount(mAccount);
02207       f->mAnnotationFolderType = "FROMSERVER";
02208       f->setNoContent(mSubfolderMimeTypes[idx] == "inode/directory");
02209       f->setNoChildren(mSubfolderMimeTypes[idx] == "message/digest");
02210       f->setImapPath(mSubfolderPaths[idx]);
02211       f->mFolderAttributes = mSubfolderAttributes[idx];
02212       kdDebug(5006) << " ####### Attributes: " << f->mFolderAttributes <<endl;
02213       //kdDebug(5006) << subfolderPath << ": mAnnotationFolderType set to FROMSERVER" << endl;
02214       kmkernel->dimapFolderMgr()->contentsChanged();
02215     } else {
02216       kdDebug(5006) << "can't create folder " << mSubfolderNames[idx] <<endl;
02217     }
02218   }
02219 
02220   kmkernel->dimapFolderMgr()->quiet(false);
02221   emit listComplete(this);
02222   if ( !mPersonalNamespacesCheckDone ) {
02223     // we're not done with the namespaces
02224     mSyncState = SYNC_STATE_LIST_NAMESPACES;
02225   }
02226   serverSyncInternal();
02227 }
02228 
02229 //-----------------------------------------------------------------------------
02230 KMFolderCachedImap* KMFolderCachedImap::findParent( const QString& path,
02231                                                     const QString& name )
02232 {
02233   QString parent = path.left( path.length() - name.length() - 2 );
02234   if ( parent.length() > 1 )
02235   {
02236     // extract name of the parent
02237     parent = parent.right( parent.length() - 1 );
02238     if ( parent != label() )
02239     {
02240       KMFolderNode *node = folder()->child()->first();
02241       // look for a better parent
02242       while ( node )
02243       {
02244         if ( node->name() == parent )
02245         {
02246           KMFolder* fld = static_cast<KMFolder*>(node);
02247           KMFolderCachedImap* imapFld =
02248             static_cast<KMFolderCachedImap*>( fld->storage() );
02249           return imapFld;
02250         }
02251         node = folder()->child()->next();
02252       }
02253     }
02254   }
02255   return 0;
02256 }
02257 
02258 void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool success)
02259 {
02260   Q_UNUSED(sub);
02261   //kdDebug(5006) << label() << " slotSubFolderComplete: " << sub->label() << endl;
02262   if ( success ) {
02263     serverSyncInternal();
02264   }
02265   else
02266   {
02267     // success == false means the sync was aborted.
02268     if ( mCurrentSubfolder ) {
02269       Q_ASSERT( sub == mCurrentSubfolder );
02270       disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
02271                   this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
02272       mCurrentSubfolder = 0;
02273     }
02274 
02275     mSubfoldersForSync.clear();
02276     mSyncState = SYNC_STATE_INITIAL;
02277     close("cachedimap");
02278     emit folderComplete( this, false );
02279   }
02280 }
02281 
02282 void KMFolderCachedImap::slotSimpleData(KIO::Job * job, const QByteArray & data)
02283 {
02284   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02285   if (it == mAccount->jobsEnd()) return;
02286   QBuffer buff((*it).data);
02287   buff.open(IO_WriteOnly | IO_Append);
02288   buff.writeBlock(data.data(), data.size());
02289   buff.close();
02290 }
02291 
02292 FolderJob*
02293 KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
02294                                  QString, const AttachmentStrategy* ) const
02295 {
02296   QPtrList<KMMessage> msgList;
02297   msgList.append( msg );
02298   CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
02299   job->setParentFolder( this );
02300   return job;
02301 }
02302 
02303 FolderJob*
02304 KMFolderCachedImap::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
02305                                  FolderJob::JobType jt, KMFolder *folder ) const
02306 {
02307   //FIXME: how to handle sets here?
02308   Q_UNUSED( sets );
02309   CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
02310   job->setParentFolder( this );
02311   return job;
02312 }
02313 
02314 void
02315 KMFolderCachedImap::setUserRights( unsigned int userRights )
02316 {
02317   mUserRights = userRights;
02318   writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
02319 }
02320 
02321 void
02322 KMFolderCachedImap::slotReceivedUserRights( KMFolder* folder )
02323 {
02324   if ( folder->storage() == this ) {
02325     disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
02326                 this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
02327     if ( mUserRights == 0 ) // didn't work
02328       mUserRights = -1; // error code (used in folderdia)
02329     else
02330       setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 );
02331     mProgress += 5;
02332     serverSyncInternal();
02333   }
02334 }
02335 
02336 void
02337 KMFolderCachedImap::setReadOnly( bool readOnly )
02338 {
02339   if ( readOnly != mReadOnly ) {
02340     mReadOnly = readOnly;
02341     emit readOnlyChanged( folder() );
02342   }
02343 }
02344 
02345 void
02346 KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job*, const KMail::ACLList& aclList )
02347 {
02348   if ( folder->storage() == this ) {
02349     disconnect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
02350                 this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
02351     mACLList = aclList;
02352     serverSyncInternal();
02353   }
02354 }
02355 
02356 void
02357 KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo& info )
02358 {
02359   setQuotaInfo( info );
02360 }
02361 
02362 void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info )
02363 {
02364     if ( info != mQuotaInfo ) {
02365       mQuotaInfo = info;
02366       writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
02367       emit folderSizeChanged();
02368     }
02369 }
02370 
02371 void
02372 KMFolderCachedImap::setACLList( const ACLList& arr )
02373 {
02374   mACLList = arr;
02375 }
02376 
02377 void
02378 KMFolderCachedImap::slotMultiSetACLResult(KIO::Job *job)
02379 {
02380   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02381   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
02382   if ( (*it).parent != folder() ) return; // Shouldn't happen
02383 
02384   if ( job->error() )
02385     // Display error but don't abort the sync just for this
02386     // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel
02387     job->showErrorDialog();
02388   else
02389     kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL );
02390 
02391   if (mAccount->slave()) mAccount->removeJob(job);
02392   serverSyncInternal();
02393 }
02394 
02395 void
02396 KMFolderCachedImap::slotACLChanged( const QString& userId, int permissions )
02397 {
02398   // The job indicates success in changing the permissions for this user
02399   // -> we note that it's been done.
02400   for( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) {
02401     if ( (*it).userId == userId && (*it).permissions == permissions ) {
02402       if ( permissions == -1 ) // deleted
02403         mACLList.erase( it );
02404       else // added/modified
02405         (*it).changed = false;
02406       return;
02407     }
02408   }
02409 }
02410 
02411 // called by KMAcctCachedImap::killAllJobs
02412 void KMFolderCachedImap::resetSyncState()
02413 {
02414   if ( mSyncState == SYNC_STATE_INITIAL ) return;
02415   mSubfoldersForSync.clear();
02416   mSyncState = SYNC_STATE_INITIAL;
02417   close("cachedimap");
02418   // Don't use newState here, it would revert to mProgress (which is < current value when listing messages)
02419   KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
02420   QString str = i18n("Aborted");
02421   if (progressItem)
02422      progressItem->setStatus( str );
02423   emit statusMsg( str );
02424 }
02425 
02426 void KMFolderCachedImap::slotIncreaseProgress()
02427 {
02428   mProgress += 5;
02429 }
02430 
02431 void KMFolderCachedImap::newState( int progress, const QString& syncStatus )
02432 {
02433   //kdDebug() << k_funcinfo << folder() << " " << mProgress << " " << syncStatus << endl;
02434   KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
02435   if( progressItem )
02436     progressItem->setCompletedItems( progress );
02437   if ( !syncStatus.isEmpty() ) {
02438     QString str;
02439     // For a subfolder, show the label. But for the main folder, it's already shown.
02440     if ( mAccount->imapFolder() == this )
02441       str = syncStatus;
02442     else
02443       str = QString( "%1: %2" ).arg( label() ).arg( syncStatus );
02444     if( progressItem )
02445       progressItem->setStatus( str );
02446     emit statusMsg( str );
02447   }
02448   if( progressItem )
02449     progressItem->updateProgress();
02450 }
02451 
02452 void KMFolderCachedImap::setSubfolderState( imapState state )
02453 {
02454   mSubfolderState = state;
02455   if ( state == imapNoInformation && folder()->child() )
02456   {
02457     // pass through to childs
02458     KMFolderNode* node;
02459     QPtrListIterator<KMFolderNode> it( *folder()->child() );
02460     for ( ; (node = it.current()); )
02461     {
02462       ++it;
02463       if (node->isDir()) continue;
02464       KMFolder *folder = static_cast<KMFolder*>(node);
02465       static_cast<KMFolderCachedImap*>(folder->storage())->setSubfolderState( state );
02466     }
02467   }
02468 }
02469 
02470 void KMFolderCachedImap::setImapPath(const QString &path)
02471 {
02472   mImapPath = path;
02473 }
02474 
02475 // mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc)
02476 // It is updated from the folder contents type and whether it's a standard resource folder.
02477 // This happens during the syncing phase and during initFolder for a new folder.
02478 // Don't do it earlier, e.g. from setContentsType:
02479 // on startup, it's too early there to know if this is a standard resource folder.
02480 void KMFolderCachedImap::updateAnnotationFolderType()
02481 {
02482   QString oldType = mAnnotationFolderType;
02483   QString oldSubType;
02484   int dot = oldType.find( '.' );
02485   if ( dot != -1 ) {
02486     oldType.truncate( dot );
02487     oldSubType = mAnnotationFolderType.mid( dot + 1 );
02488   }
02489 
02490   QString newType, newSubType;
02491   // We want to store an annotation on the folder only if using the kolab storage.
02492   if ( kmkernel->iCalIface().storageFormat( folder() ) == KMailICalIfaceImpl::StorageXML ) {
02493     newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType );
02494     if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) )
02495       newSubType = "default";
02496     else
02497       newSubType = oldSubType; // preserve unknown subtypes, like drafts etc. And preserve ".default" too.
02498   }
02499 
02500   //kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: " << newType << " " << newSubType << endl;
02501   if ( newType != oldType || newSubType != oldSubType ) {
02502     mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString::null : "."+newSubType );
02503     mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync
02504     kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl;
02505   }
02506   // Ensure that further readConfig()s don't lose mAnnotationFolderType
02507   writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
02508 }
02509 
02510 void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor )
02511 {
02512   if ( mIncidencesFor != incfor ) {
02513     mIncidencesFor = incfor;
02514     mIncidencesForChanged = true;
02515   }
02516 }
02517 
02518 void KMFolderCachedImap::slotAnnotationResult(const QString& entry, const QString& value, bool found)
02519 {
02520   if ( entry == KOLAB_FOLDERTYPE ) {
02521     // There are four cases.
02522     // 1) no content-type on server -> set it
02523     // 2) different content-type on server, locally changed -> set it (we don't even come here)
02524     // 3) different (known) content-type on server, no local change -> get it
02525     // 4) different unknown content-type on server, probably some older version -> set it
02526     if ( found ) {
02527       QString type = value;
02528       QString subtype;
02529       int dot = value.find( '.' );
02530       if ( dot != -1 ) {
02531         type.truncate( dot );
02532         subtype = value.mid( dot + 1 );
02533       }
02534       bool foundKnownType = false;
02535       for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) {
02536         FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i );
02537         if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) {
02538           // Case 3: known content-type on server, get it
02539           //kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: found known type of annotation" << endl;
02540           if ( contentsType != ContentsTypeMail )
02541             kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML );
02542           mAnnotationFolderType = value;
02543           if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent()
02544                && GlobalSettings::self()->theIMAPResourceEnabled()
02545                && subtype == "default" ) {
02546             // Truncate subtype if this folder can't be a default resource folder for us,
02547             // although it apparently is for someone else.
02548             mAnnotationFolderType = type;
02549             kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: parent folder is " << folder()->parent()->owner()->idString() << " => truncating annotation to " << value << endl;
02550           }
02551           setContentsType( contentsType );
02552           mAnnotationFolderTypeChanged = false; // we changed it, not the user
02553           foundKnownType = true;
02554 
02555           // Users don't read events/contacts/etc. in kmail, so mark them all as read.
02556           // This is done in cachedimapjob when getting new messages, but do it here too,
02557           // for the initial set of messages when we didn't know this was a resource folder yet,
02558           // for old folders, etc.
02559           if ( contentsType != ContentsTypeMail )
02560             markUnreadAsRead();
02561 
02562           // Ensure that further readConfig()s don't lose mAnnotationFolderType
02563           writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
02564           break;
02565         }
02566       }
02567       if ( !foundKnownType && !mReadOnly ) {
02568         //kdDebug(5006) << "slotGetAnnotationResult: no known type of annotation found, will need to set it" << endl;
02569         // Case 4: server has strange content-type, set it to what we need
02570         mAnnotationFolderTypeChanged = true;
02571       }
02572       // TODO handle subtype (inbox, drafts, sentitems, junkemail)
02573     }
02574     else if ( !mReadOnly ) {
02575       // Case 1: server doesn't have content-type, set it
02576       //kdDebug(5006) << "slotGetAnnotationResult: no annotation found, will need to set it" << endl;
02577       mAnnotationFolderTypeChanged = true;
02578     }
02579   } else if ( entry == KOLAB_INCIDENCESFOR ) {
02580     if ( found ) {
02581       mIncidencesFor = incidencesForFromString( value );
02582       Q_ASSERT( mIncidencesForChanged == false );
02583     }
02584   }
02585 }
02586 
02587 void KMFolderCachedImap::slotGetAnnotationResult( KIO::Job* job )
02588 {
02589   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02590   Q_ASSERT( it != mAccount->jobsEnd() );
02591   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
02592   Q_ASSERT( (*it).parent == folder() );
02593   if ( (*it).parent != folder() ) return; // Shouldn't happen
02594 
02595   AnnotationJobs::GetAnnotationJob* annjob = static_cast<AnnotationJobs::GetAnnotationJob *>( job );
02596   if ( annjob->error() ) {
02597     if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
02598       // that's when the imap server doesn't support annotations
02599       if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML
02600            && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() )
02601     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() ) );
02602       mAccount->setHasNoAnnotationSupport();
02603     }
02604     else
02605       kdWarning(5006) << "slotGetAnnotationResult: " << job->errorString() << endl;
02606   }
02607 
02608   if (mAccount->slave()) mAccount->removeJob(job);
02609   mProgress += 2;
02610   serverSyncInternal();
02611 }
02612 
02613 void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KIO::Job* job )
02614 {
02615   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02616   Q_ASSERT( it != mAccount->jobsEnd() );
02617   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
02618   Q_ASSERT( (*it).parent == folder() );
02619   if ( (*it).parent != folder() ) return; // Shouldn't happen
02620 
02621   QValueVector<int> folders;
02622   AnnotationJobs::MultiUrlGetAnnotationJob* annjob
02623     = static_cast<AnnotationJobs::MultiUrlGetAnnotationJob *>( job );
02624   if ( annjob->error() ) {
02625     if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
02626       // that's when the imap server doesn't support annotations
02627       if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML
02628            && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() )
02629         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() ) );
02630       mAccount->setHasNoAnnotationSupport();
02631     }
02632     else
02633       kdWarning(5006) << "slotGetMultiUrlAnnotationResult: " << job->errorString() << endl;
02634   } else {
02635     // we got the annotation allright, let's filter out the ones with the wrong type
02636     QMap<QString, QString> annotations = annjob->annotations();
02637     QMap<QString, QString>::Iterator it = annotations.begin();
02638     for ( ; it != annotations.end(); ++it ) {
02639       const QString folderPath = it.key();
02640       const QString annotation = it.data();
02641       kdDebug(5006) << k_funcinfo << "Folder: " << folderPath << " has type: " << annotation << endl;
02642       // we're only interested in the main type
02643       QString type(annotation);
02644       int dot = annotation.find( '.' );
02645       if ( dot != -1 ) type.truncate( dot );
02646       type = type.simplifyWhiteSpace();
02647 
02648       const int idx = mSubfolderPaths.findIndex( folderPath );
02649       const bool isNoContent =  mSubfolderMimeTypes[idx] == "inode/directory";
02650       if ( ( isNoContent && type.isEmpty() )
02651         || ( !type.isEmpty() && type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) {
02652         folders.append( idx );
02653         kdDebug(5006) << k_funcinfo << " subscribing to: " << folderPath << endl;
02654       } else {
02655         kdDebug(5006) << k_funcinfo << " automatically unsubscribing from: " << folderPath << endl;
02656         mAccount->changeLocalSubscription( folderPath, false );
02657       }
02658     }
02659   }
02660 
02661   if (mAccount->slave()) mAccount->removeJob(job);
02662   createFoldersNewOnServerAndFinishListing( folders );
02663 }
02664 
02665 void KMFolderCachedImap::slotQuotaResult( KIO::Job* job )
02666 {
02667   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02668   Q_ASSERT( it != mAccount->jobsEnd() );
02669   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
02670   Q_ASSERT( (*it).parent == folder() );
02671   if ( (*it).parent != folder() ) return; // Shouldn't happen
02672 
02673   QuotaJobs::GetStorageQuotaJob* quotajob = static_cast<QuotaJobs::GetStorageQuotaJob *>( job );
02674   QuotaInfo empty;
02675   if ( quotajob->error() ) {
02676     if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
02677       // that's when the imap server doesn't support quota
02678       mAccount->setHasNoQuotaSupport();
02679       setQuotaInfo( empty );
02680     }
02681     else
02682       kdWarning(5006) << "slotGetQuotaResult: " << job->errorString() << endl;
02683   }
02684 
02685   if (mAccount->slave()) mAccount->removeJob(job);
02686   mProgress += 2;
02687   serverSyncInternal();
02688 }
02689 
02690 void
02691 KMFolderCachedImap::slotAnnotationChanged( const QString& entry, const QString& attribute, const QString& value )
02692 {
02693   Q_UNUSED( attribute );
02694   Q_UNUSED( value );
02695   //kdDebug(5006) << k_funcinfo << entry << " " << attribute << " " << value << endl;
02696   if ( entry == KOLAB_FOLDERTYPE )
02697     mAnnotationFolderTypeChanged = false;
02698   else if ( entry == KOLAB_INCIDENCESFOR ) {
02699     mIncidencesForChanged = false;
02700     // The incidences-for changed, we must trigger the freebusy creation.
02701     // HACK: in theory we would need a new enum value for this.
02702     kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL );
02703   }
02704 }
02705 
02706 void KMFolderCachedImap::slotTestAnnotationResult(KIO::Job *job)
02707 {
02708   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02709   Q_ASSERT( it != mAccount->jobsEnd() );
02710   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
02711   Q_ASSERT( (*it).parent == folder() );
02712   if ( (*it).parent != folder() ) return; // Shouldn't happen
02713 
02714   mAccount->setAnnotationCheckPassed( true );
02715   if ( job->error() ) {
02716     kdDebug(5006) << "Test Annotation was not passed, disabling annotation support" << endl;
02717     mAccount->setHasNoAnnotationSupport( );
02718   } else {
02719     kdDebug(5006) << "Test Annotation was passed   OK" << endl;
02720   }
02721   if (mAccount->slave()) mAccount->removeJob(job);
02722   serverSyncInternal();
02723 }
02724 
02725 void
02726 KMFolderCachedImap::slotSetAnnotationResult(KIO::Job *job)
02727 {
02728   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
02729   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
02730   if ( (*it).parent != folder() ) return; // Shouldn't happen
02731 
02732   bool cont = true;
02733   if ( job->error() ) {
02734     // Don't show error if the server doesn't support ANNOTATEMORE and this folder only contains mail
02735     if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION && contentsType() == ContentsTypeMail ) {
02736       if (mAccount->slave()) mAccount->removeJob(job);
02737     } else {
02738       cont = mAccount->handleJobError( job, i18n( "Error while setting annotation: " ) + '\n' );
02739     }
02740   } else {
02741     if (mAccount->slave()) mAccount->removeJob(job);
02742   }
02743   if ( cont )
02744     serverSyncInternal();
02745 }
02746 
02747 void KMFolderCachedImap::slotUpdateLastUid()
02748 {
02749   if( mTentativeHighestUid != 0 ) {
02750 
02751       // Sanity checking:
02752       // By now all new mails should be downloaded, which means
02753       // that iteration over the folder should yield only UIDs
02754       // lower or equal to what we think the highes ist, and the
02755       // highest one as well. If not, our notion of the highest
02756       // uid we've seen thus far is wrong, which is dangerous, so
02757       // don't update the mLastUid, then.
02758       // Not entirely true though, mails might have been moved out
02759       // of the folder already by filters, thus giving us a higher tentative
02760       // uid than we actually observe here.
02761       bool sane = count() == 0;
02762 
02763       for (int i=0;i<count(); i++ ) {
02764           ulong uid = getMsgBase(i)->UID();
02765           if ( uid > mTentativeHighestUid && uid > lastUid() ) {
02766               kdWarning(5006) << "DANGER: Either the server listed a wrong highest uid, "
02767                   "or we parsed it wrong. Send email to adam@kde.org, please, and include this log." << endl;
02768               kdWarning(5006) << "uid: " << uid << " mTentativeHighestUid: " << mTentativeHighestUid << endl;
02769               assert( false );
02770               break;
02771           } else {
02772               sane = true;
02773           }
02774       }
02775       if (sane) {
02776 #if MAIL_LOSS_DEBUGGING
02777           kdDebug(5006) << "Tentative highest UID test was sane, writing out: " << mTentativeHighestUid << endl;
02778 #endif
02779           setLastUid( mTentativeHighestUid );
02780       }
02781   }
02782   mTentativeHighestUid = 0;
02783 }
02784 
02785 bool KMFolderCachedImap::isMoveable() const
02786 {
02787   return ( hasChildren() == HasNoChildren &&
02788       !folder()->isSystemFolder() ) ? true : false;
02789 }
02790 
02791 void KMFolderCachedImap::slotFolderDeletionOnServerFinished()
02792 {
02793   for ( QStringList::const_iterator it = foldersForDeletionOnServer.constBegin();
02794       it != foldersForDeletionOnServer.constEnd(); ++it ) {
02795     KURL url( mAccount->getUrl() );
02796     url.setPath( *it );
02797     kmkernel->iCalIface().folderDeletedOnServer( url );
02798   }
02799   serverSyncInternal();
02800 }
02801 
02802 int KMFolderCachedImap::createIndexFromContentsRecursive()
02803 {
02804   if ( !folder() || !folder()->child() )
02805     return 0;
02806 
02807   KMFolderNode *node = 0;
02808   for( QPtrListIterator<KMFolderNode> it( *folder()->child() ); (node = it.current()); ++it ) {
02809     if( !node->isDir() ) {
02810       KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
02811       kdDebug() << k_funcinfo << "Re-indexing: " << storage->folder()->label() << endl;
02812       int rv = storage->createIndexFromContentsRecursive();
02813       if ( rv > 0 )
02814         return rv;
02815     }
02816   }
02817 
02818   return createIndexFromContents();
02819 }
02820 
02821 void KMFolderCachedImap::setAlarmsBlocked( bool blocked )
02822 {
02823   mAlarmsBlocked = blocked;
02824 }
02825 
02826 bool KMFolderCachedImap::alarmsBlocked() const
02827 {
02828   return mAlarmsBlocked;
02829 }
02830 
02831 bool KMFolderCachedImap::isCloseToQuota() const
02832 {
02833   bool closeToQuota = false;
02834   if ( mQuotaInfo.isValid() && mQuotaInfo.max().toInt() > 0 ) {
02835     const int ratio = mQuotaInfo.current().toInt() * 100  / mQuotaInfo.max().toInt();
02836     //kdDebug(5006) << "Quota ratio: " << ratio << "% " << mQuotaInfo.toString() << endl;
02837     closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() );
02838   }
02839   //kdDebug(5006) << "Folder: " << folder()->prettyURL() << " is over quota: " << closeToQuota << endl;
02840   return closeToQuota;
02841 }
02842 
02843 KMCommand* KMFolderCachedImap::rescueUnsyncedMessages()
02844 {
02845   QValueList<unsigned long> newMsgs = findNewMessages();
02846   kdDebug() << k_funcinfo << newMsgs << " of " << count() << endl;
02847   if ( newMsgs.isEmpty() )
02848     return 0;
02849   KMFolder *dest = 0;
02850   bool manualMove = true;
02851   while ( GlobalSettings::autoLostFoundMove() ) {
02852     // find the inbox of this account
02853     KMFolder *inboxFolder = kmkernel->findFolderById( QString(".%1.directory/INBOX").arg( account()->id() ) );
02854     if ( !inboxFolder ) {
02855       kdWarning(5006) << k_funcinfo << "inbox not found!" << endl;
02856       break;
02857     }
02858     KMFolderDir *inboxDir = inboxFolder->child();
02859     if ( !inboxDir && !inboxFolder->storage() )
02860       break;
02861     assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap );
02862 
02863     // create lost+found folder if needed
02864     KMFolderNode *node;
02865     KMFolder *lfFolder = 0;
02866     if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) {
02867       kdDebug(5006) << k_funcinfo << "creating lost+found folder" << endl;
02868       KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder(
02869           i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir );
02870       if ( !folder || !folder->storage() )
02871         break;
02872       static_cast<KMFolderCachedImap*>( folder->storage() )->initializeFrom(
02873         static_cast<KMFolderCachedImap*>( inboxFolder->storage() ) );
02874       folder->storage()->setContentsType( KMail::ContentsTypeMail );
02875       folder->storage()->writeConfig();
02876       lfFolder = folder;
02877     } else {
02878       kdDebug(5006) << k_funcinfo << "found lost+found folder" << endl;
02879       lfFolder = dynamic_cast<KMFolder*>( node );
02880     }
02881     if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() )
02882       break;
02883 
02884     // create subfolder for this incident
02885     QDate today = QDate::currentDate();
02886     QString baseName = folder()->label() + "-" + QString::number( today.year() )
02887         + (today.month() < 10 ? "0" : "" ) + QString::number( today.month() )
02888         + (today.day() < 10 ? "0" : "" ) + QString::number( today.day() );
02889     QString name = baseName;
02890     int suffix = 0;
02891     while ( (node = lfFolder->child()->hasNamedFolder( name )) ) {
02892       ++suffix;
02893       name = baseName + '-' + QString::number( suffix );
02894     }
02895     kdDebug(5006) << k_funcinfo << "creating lost+found folder " << name << endl;
02896     dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() );
02897     if ( !dest || !dest->storage() )
02898         break;
02899     static_cast<KMFolderCachedImap*>( dest->storage() )->initializeFrom(
02900       static_cast<KMFolderCachedImap*>( lfFolder->storage() ) );
02901     dest->storage()->setContentsType( contentsType() );
02902     dest->storage()->writeConfig();
02903 
02904     KMessageBox::sorry( 0, i18n("<p>There are new messages in folder <b>%1</b>, which "
02905           "have not been uploaded to the server yet, but the folder has been deleted "
02906           "on the server or you do not "
02907           "have sufficient access rights on the folder to upload them.</p>"
02908           "<p>All affected messages will therefore be moved to <b>%2</b> "
02909           "to avoid data loss.</p>").arg( folder()->prettyURL() ).arg( dest->prettyURL() ),
02910           i18n("Insufficient access rights") );
02911     manualMove = false;
02912     break;
02913   }
02914 
02915   if ( manualMove ) {
02916     const QString msg ( i18n( "<p>There are new messages in this folder (%1), which "
02917           "have not been uploaded to the server yet, but the folder has been deleted "
02918           "on the server or you do not "
02919           "have sufficient access rights on the folder now to upload them. "
02920           "Please contact your administrator to allow upload of new messages "
02921           "to you, or move them out of this folder.</p> "
02922           "<p>Do you want to move these messages to another folder now?</p>").arg( folder()->prettyURL() ) );
02923     if ( KMessageBox::warningYesNo( 0, msg, QString::null, i18n("Move"), i18n("Do Not Move") ) == KMessageBox::Yes ) {
02924       KMail::KMFolderSelDlg dlg( kmkernel->getKMMainWidget(),
02925           i18n("Move Messages to Folder"), true );
02926       if ( dlg.exec() ) {
02927         dest = dlg.folder();
02928       }
02929     }
02930   }
02931   if ( dest ) {
02932     QPtrList<KMMsgBase> msgs;
02933     for( int i = 0; i < count(); ++i ) {
02934       KMMsgBase *msg = getMsgBase( i );
02935       if( !msg ) continue; /* what goes on if getMsg() returns 0? */
02936       if ( msg->UID() == 0 )
02937         msgs.append( msg );
02938     }
02939     KMCommand *command = new KMMoveCommand( dest, msgs );
02940     command->start();
02941     return command;
02942   }
02943   return 0;
02944 }
02945 
02946 void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root )
02947 {
02948   kdDebug() << k_funcinfo << folder << " " << root << endl;
02949   if ( root )
02950     mToBeDeletedAfterRescue.append( folder );
02951   folder->open("cachedimap");
02952   KMFolderCachedImap* storage = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
02953   if ( storage ) {
02954     KMCommand *command = storage->rescueUnsyncedMessages();
02955     if ( command ) {
02956       connect( command, SIGNAL(completed(KMCommand*)),
02957                SLOT(slotRescueDone(KMCommand*)) );
02958       ++mRescueCommandCount;
02959     } else {
02960       // nothing to rescue, close folder
02961       // (we don't need to close it in the other case, it will be deleted anyway)
02962       folder->close("cachedimap");
02963     }
02964   }
02965   if ( folder->child() ) {
02966     KMFolderNode *node = folder->child()->first();
02967     while (node) {
02968       if (!node->isDir() ) {
02969         KMFolder *subFolder = static_cast<KMFolder*>( node );
02970         rescueUnsyncedMessagesAndDeleteFolder( subFolder, false );
02971       }
02972       node = folder->child()->next();
02973     }
02974   }
02975 }
02976 
02977 void KMFolderCachedImap::slotRescueDone(KMCommand * command)
02978 {
02979   // FIXME: check command result
02980   if ( command )
02981     --mRescueCommandCount;
02982   if ( mRescueCommandCount > 0 )
02983     return;
02984   for ( QValueList<KMFolder*>::ConstIterator it = mToBeDeletedAfterRescue.constBegin();
02985         it != mToBeDeletedAfterRescue.constEnd(); ++it ) {
02986     kmkernel->dimapFolderMgr()->remove( *it );
02987   }
02988   mToBeDeletedAfterRescue.clear();
02989   serverSyncInternal();
02990 }
02991 
02992 #include "kmfoldercachedimap.moc"