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