kmail

kmfoldercachedimap.cpp

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