kmail Library API Documentation

kmfoldercachedimap.cpp

00001 
00032 #ifdef HAVE_CONFIG_H
00033 #include <config.h>
00034 #endif
00035 
00036 #include <errno.h>
00037 
00038 #include "kmkernel.h"
00039 #include "kmfoldercachedimap.h"
00040 #include "undostack.h"
00041 #include "kmfoldermgr.h"
00042 #include "kmmessage.h"
00043 #include "kmacctcachedimap.h"
00044 #include "kmacctmgr.h"
00045 #include "kmailicalifaceimpl.h"
00046 #include "kmfolder.h"
00047 #include "kmdict.h"
00048 #include "acljobs.h"
00049 #include "broadcaststatus.h"
00050 using KPIM::BroadcastStatus;
00051 #include "progressmanager.h"
00052 
00053 using KMail::CachedImapJob;
00054 using KMail::ImapAccountBase;
00055 #include "listjob.h"
00056 using KMail::ListJob;
00057 
00058 #include <kapplication.h>
00059 #include <kmessagebox.h>
00060 #include <klocale.h>
00061 #include <kdebug.h>
00062 #include <kconfig.h>
00063 #include <kio/global.h>
00064 #include <kio/scheduler.h>
00065 #include <qbuffer.h>
00066 #include <qfile.h>
00067 #include <qlabel.h>
00068 #include <qlayout.h>
00069 #include <qvaluelist.h>
00070 
00071 #define UIDCACHE_VERSION 1
00072 
00073 
00074 DImapTroubleShootDialog::DImapTroubleShootDialog( QWidget* parent,
00075                                                   const char* name )
00076   : KDialogBase( Plain, i18n( "Troubleshooting IMAP Cache" ),
00077                  Cancel | User1 | User2, Cancel, parent, name, true ),
00078     rc( Cancel )
00079 {
00080   QFrame* page = plainPage();
00081   QVBoxLayout *topLayout = new QVBoxLayout( page, 0 );
00082   QString txt = i18n( "<p><b>Troubleshooting the IMAP cache.</b></p>"
00083                       "<p>If you have problems with synchronizing an IMAP "
00084                       "folder, you should first try rebuilding the index "
00085                       "file. This will take some time to rebuild, but will "
00086                       "not cause any problems.</p><p>If that is not enough, "
00087                       "you can try refreshing the IMAP cache. If you do this, "
00088                       "you will loose all your local changes for this folder "
00089                       "and all it's subfolders.</p>" );
00090   topLayout->addWidget( new QLabel( txt, page ) );
00091   enableButtonSeparator( true );
00092 
00093   setButtonText( User1, i18n( "Refresh &Cache" ) );
00094   setButtonText( User2, i18n( "Rebuild &Index" ) );
00095 
00096   connect( this, SIGNAL( user1Clicked () ), this, SLOT( slotRebuildCache() ) );
00097   connect( this, SIGNAL( user2Clicked () ), this, SLOT( slotRebuildIndex() ) );
00098 }
00099 
00100 int DImapTroubleShootDialog::run()
00101 {
00102   DImapTroubleShootDialog d;
00103   d.exec();
00104   return d.rc;
00105 }
00106 
00107 void DImapTroubleShootDialog::slotRebuildCache()
00108 {
00109   rc = User1;
00110   done( User1 );
00111 }
00112 
00113 void DImapTroubleShootDialog::slotRebuildIndex()
00114 {
00115   rc = User2;
00116   done( User2 );
00117 }
00118 
00119 
00120 KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName )
00121   : KMFolderMaildir( folder, aName ),
00122     mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ),
00123     mSubfolderState( imapNoInformation ), mIsSelected( false ),
00124     mCheckFlags( true ), mAccount( NULL ), uidMapDirty( true ),
00125     uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ),
00126     mUserRights( 0 ), mFolderRemoved( false ),
00127     /*mHoldSyncs( false ),*/ mRecurse( true ),
00128     mContentsTypeChanged( false ), mStatusChangedLocally( false )
00129 {
00130   setUidValidity("");
00131   readUidCache();
00132 
00133   mProgress = 0;
00134 }
00135 
00136 KMFolderCachedImap::~KMFolderCachedImap()
00137 {
00138   if( !mFolderRemoved ) {
00139     // Only write configuration when the folder haven't been deleted
00140     KConfig* config = KMKernel::config();
00141     KConfigGroupSaver saver( config, "Folder-" + folder()->idString() );
00142     config->writeEntry( "ImapPath", mImapPath );
00143     config->writeEntry( "NoContent", mNoContent );
00144     config->writeEntry( "ReadOnly", mReadOnly );
00145     config->writeEntry( "StatusChangedLocally", mStatusChangedLocally );
00146 
00147     writeUidCache();
00148   }
00149 
00150   if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
00151 }
00152 
00153 void KMFolderCachedImap::initializeFrom( KMFolderCachedImap* parent )
00154 {
00155   setAccount( parent->account() );
00156   // Now that we have an account, tell it that this folder was created:
00157   // if this folder was just removed, then we don't really want to remove it from the server.
00158   mAccount->removeDeletedFolder( imapPath() );
00159   setUserRights( parent->userRights() );
00160 }
00161 
00162 void KMFolderCachedImap::readConfig()
00163 {
00164   KConfig* config = KMKernel::config();
00165   KConfigGroupSaver saver( config, "Folder-" + folder()->idString() );
00166   if( mImapPath.isEmpty() ) mImapPath = config->readEntry( "ImapPath" );
00167   if( QString( name() ).upper() == "INBOX" && mImapPath == "/INBOX/" )
00168   {
00169     folder()->setLabel( i18n( "inbox" ) );
00170     // for the icon
00171     folder()->setSystemFolder( true );
00172   }
00173   mNoContent = config->readBoolEntry( "NoContent", false );
00174   mReadOnly = config->readBoolEntry( "ReadOnly", false );
00175 
00176   KMFolderMaildir::readConfig();
00177   mContentsTypeChanged = false;
00178   mStatusChangedLocally =
00179     config->readBoolEntry( "StatusChangedLocally", false );
00180 }
00181 
00182 void KMFolderCachedImap::remove()
00183 {
00184   mFolderRemoved = true;
00185 
00186   QString part1 = folder()->path() + "/." + dotEscape(name());
00187   QString uidCacheFile = part1 + ".uidcache";
00188   // This is the account folder of an account that was just removed
00189   // When this happens, be sure to delete all traces of the cache
00190   if( QFile::exists(uidCacheFile) )
00191     unlink( QFile::encodeName( uidCacheFile ) );
00192   KIO::del( KURL::fromPathOrURL( part1 + ".directory" ) );
00193 
00194   FolderStorage::remove();
00195 }
00196 
00197 QString KMFolderCachedImap::uidCacheLocation() const
00198 {
00199   QString sLocation(folder()->path());
00200   if (!sLocation.isEmpty()) sLocation += '/';
00201   return sLocation + '.' + dotEscape(fileName()) + ".uidcache";
00202 }
00203 
00204 int KMFolderCachedImap::readUidCache()
00205 {
00206   QFile uidcache( uidCacheLocation() );
00207   if( uidcache.open( IO_ReadOnly ) ) {
00208     char buf[1024];
00209     int len = uidcache.readLine( buf, sizeof(buf) );
00210     if( len > 0 ) {
00211       int cacheVersion;
00212       sscanf( buf, "# KMail-UidCache V%d\n",  &cacheVersion );
00213       if( cacheVersion == UIDCACHE_VERSION ) {
00214         len = uidcache.readLine( buf, sizeof(buf) );
00215         if( len > 0 ) {
00216           setUidValidity( QString::fromLocal8Bit( buf).stripWhiteSpace() );
00217           len = uidcache.readLine( buf, sizeof(buf) );
00218           if( len > 0 ) {
00219             // load the last known highest uid from the on disk cache
00220             setLastUid( QString::fromLocal8Bit( buf).stripWhiteSpace().toULong() );
00221             return 0;
00222           }
00223         }
00224       }
00225     }
00226   }
00227   return -1;
00228 }
00229 
00230 int KMFolderCachedImap::writeUidCache()
00231 {
00232   if( uidValidity().isEmpty() || uidValidity() == "INVALID" ) {
00233     // No info from the server yet, remove the file.
00234     if( QFile::exists( uidCacheLocation() ) )
00235       unlink( QFile::encodeName( uidCacheLocation() ) );
00236     return 0;
00237   }
00238 
00239   QFile uidcache( uidCacheLocation() );
00240   if( uidcache.open( IO_WriteOnly ) ) {
00241     QTextStream str( &uidcache );
00242     str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl;
00243     str << uidValidity() << endl;
00244     str << lastUid() << endl;
00245     uidcache.flush();
00246     fsync( uidcache.handle() ); /* this is probably overkill */
00247     uidcache.close();
00248     return 0;
00249   } else {
00250     return errno; /* does QFile set errno? */
00251   }
00252 }
00253 
00254 void KMFolderCachedImap::reloadUidMap()
00255 {
00256   uidMap.clear();
00257   open();
00258   for( int i = 0; i < count(); ++i ) {
00259     KMMsgBase *msg = getMsgBase( i );
00260     if( !msg ) continue;
00261     ulong uid = msg->UID();
00262     uidMap.insert( uid, i );
00263   }
00264   close();
00265   uidMapDirty = false;
00266 }
00267 
00268 /* Reimplemented from KMFolderMaildir */
00269 KMMessage* KMFolderCachedImap::take(int idx)
00270 {
00271   uidMapDirty = true;
00272   return KMFolderMaildir::take(idx);
00273 }
00274 
00275 // Add a message without clearing it's X-UID field.
00276 int KMFolderCachedImap::addMsgInternal( KMMessage* msg, bool newMail,
00277                                         int* index_return )
00278 {
00279   // Possible optimization: Only dirty if not filtered below
00280   ulong uid = msg->UID();
00281   if( uid != 0 ) {
00282     uidMapDirty = true;
00283   }
00284 
00285   // Add the message
00286   int rc = KMFolderMaildir::addMsg(msg, index_return);
00287 
00288   if( newMail && imapPath() == "/INBOX/" )
00289     // This is a new message. Filter it
00290     mAccount->processNewMsg( msg );
00291 
00292   return rc;
00293 }
00294 
00295 /* Reimplemented from KMFolderMaildir */
00296 int KMFolderCachedImap::addMsg(KMMessage* msg, int* index_return)
00297 {
00298   // Strip the IMAP UID
00299   msg->removeHeaderField( "X-UID" );
00300   msg->setUID( 0 );
00301 
00302   // Add it to storage
00303   return addMsgInternal( msg, false, index_return );
00304 }
00305 
00306 
00307 /* Reimplemented from KMFolderMaildir */
00308 void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet)
00309 {
00310   uidMapDirty = true;
00311   // Remove it from disk
00312   KMFolderMaildir::removeMsg(idx,imapQuiet);
00313 }
00314 
00315 bool KMFolderCachedImap::canRemoveFolder() const {
00316   // If this has subfolders it can't be removed
00317   if( folder() && folder()->child() && folder()->child()->count() > 0 )
00318     return false;
00319 
00320 #if 0
00321   // No special condition here, so let base class decide
00322   return KMFolderMaildir::canRemoveFolder();
00323 #endif
00324   return true;
00325 }
00326 
00327 /* Reimplemented from KMFolderDir */
00328 int KMFolderCachedImap::rename( const QString& aName,
00329                                 KMFolderDir* /*aParent*/ )
00330 {
00331   if ( aName == name() )
00332     // Stupid user trying to rename it to it's old name :)
00333     return 0;
00334 
00335   if( account() == 0 || imapPath().isEmpty() ) { // I don't think any of this can happen anymore
00336     QString err = i18n("You must synchronize with the server before renaming IMAP folders.");
00337     KMessageBox::error( 0, err );
00338     return -1;
00339   }
00340 
00341   // Make the change appear to the user with setLabel, but we'll do the change
00342   // on the server during the next sync.
00343   mAccount->addRenamedFolder( imapPath(), folder()->label(), aName );
00344   folder()->setLabel( aName );
00345 
00346   return 0;
00347 }
00348 
00349 KMFolder* KMFolderCachedImap::trashFolder() const
00350 {
00351   QString trashStr = account()->trash();
00352   return kmkernel->dimapFolderMgr()->findIdString( trashStr );
00353 }
00354 
00355 void KMFolderCachedImap::setLastUid( ulong uid )
00356 {
00357   mLastUid = uid;
00358   if( uidWriteTimer == -1 )
00359     // Write in one minute
00360     uidWriteTimer = startTimer( 60000 );
00361 }
00362 
00363 void KMFolderCachedImap::timerEvent( QTimerEvent* )
00364 {
00365   killTimer( uidWriteTimer );
00366   uidWriteTimer = -1;
00367   writeUidCache();
00368 }
00369 
00370 ulong KMFolderCachedImap::lastUid()
00371 {
00372   return mLastUid;
00373 }
00374 
00375 KMMsgBase* KMFolderCachedImap::findByUID( ulong uid )
00376 {
00377   bool mapReloaded = false;
00378   if( uidMapDirty ) {
00379     reloadUidMap();
00380     mapReloaded = true;
00381   }
00382 
00383   QMap<ulong,int>::Iterator it = uidMap.find( uid );
00384   if( it != uidMap.end() ) {
00385     KMMsgBase *msg = getMsgBase( *it );
00386     if( msg && msg->UID() == uid )
00387       return msg;
00388   }
00389   // Not found by now
00390   if( mapReloaded )
00391     // Not here then
00392     return 0;
00393   // There could be a problem in the maps. Rebuild them and try again
00394   reloadUidMap();
00395   it = uidMap.find( uid );
00396   if( it != uidMap.end() )
00397     // Since the uid map is just rebuilt, no need for the sanity check
00398     return getMsg( *it );
00399   // Then it's not here
00400   return 0;
00401 }
00402 
00403 // This finds and sets the proper account for this folder if it has
00404 // not been done
00405 KMAcctCachedImap *KMFolderCachedImap::account() const
00406 {
00407   if( (KMAcctCachedImap *)mAccount == 0 ) {
00408     // Find the account
00409     mAccount = static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( name() ) );
00410   }
00411 
00412   return mAccount;
00413 }
00414 
00415 void KMFolderCachedImap::slotTroubleshoot()
00416 {
00417   const int rc = DImapTroubleShootDialog::run();
00418 
00419   if( rc == KDialogBase::User1 ) {
00420     // Refresh cache
00421     if( !account() ) {
00422       KMessageBox::sorry( 0, i18n("No account setup for this folder.\n"
00423                                   "Please try running a sync before this.") );
00424       return;
00425     }
00426     QString str = i18n("Are you sure you want to refresh the IMAP cache of "
00427                        "the folder %1 and all it's subfolders?\nThis will "
00428                        "remove all changes you have done locally to your "
00429                        "folders").arg( label() );
00430     QString s1 = i18n("Refresh IMAP Cache");
00431     QString s2 = i18n("&Refresh");
00432     if( KMessageBox::warningContinueCancel( 0, str, s1, s2 ) ==
00433         KMessageBox::Continue )
00434       account()->invalidateIMAPFolders( this );
00435   } else if( rc == KDialogBase::User2 ) {
00436     // Rebuild index file
00437     createIndexFromContents();
00438     KMessageBox::information( 0, i18n( "The index of this folder has been "
00439                                        "recreated." ) );
00440   }
00441 }
00442 
00443 void KMFolderCachedImap::serverSync( bool recurse )
00444 {
00445   if( mSyncState != SYNC_STATE_INITIAL ) {
00446     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 ) ) == KMessageBox::Yes ) {
00447       mSyncState = SYNC_STATE_INITIAL;
00448     } else return;
00449   }
00450 
00451   mRecurse = recurse;
00452   assert( account() );
00453 
00454   mAccount->mailCheckProgressItem()->reset();
00455   mAccount->mailCheckProgressItem()->setTotalItems( 100 );
00456   mProgress = 0;
00457 
00458 #if 0
00459   if( mHoldSyncs ) {
00460     // All done for this folder.
00461     account()->mailCheckProgressItem()->setProgress( 100 );
00462     mProgress = 100; // all done
00463     newState( mProgress, i18n("Synchronization skipped"));
00464     mSyncState = SYNC_STATE_INITIAL;
00465     emit folderComplete( this, true );
00466     return;
00467   }
00468 #endif
00469   mTentativeHighestUid = 0; // reset, last sync could have been canceled
00470 
00471   serverSyncInternal();
00472 }
00473 
00474 QString KMFolderCachedImap::state2String( int state ) const
00475 {
00476   switch( state ) {
00477   case SYNC_STATE_INITIAL:           return "SYNC_STATE_INITIAL";
00478   case SYNC_STATE_PUT_MESSAGES:      return "SYNC_STATE_PUT_MESSAGES";
00479   case SYNC_STATE_UPLOAD_FLAGS:      return "SYNC_STATE_UPLOAD_FLAGS";
00480   case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS";
00481   case SYNC_STATE_LIST_SUBFOLDERS:   return "SYNC_STATE_LIST_SUBFOLDERS";
00482   case SYNC_STATE_LIST_SUBFOLDERS2:  return "SYNC_STATE_LIST_SUBFOLDERS2";
00483   case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS";
00484   case SYNC_STATE_LIST_MESSAGES:     return "SYNC_STATE_LIST_MESSAGES";
00485   case SYNC_STATE_DELETE_MESSAGES:   return "SYNC_STATE_DELETE_MESSAGES";
00486   case SYNC_STATE_GET_MESSAGES:      return "SYNC_STATE_GET_MESSAGES";
00487   case SYNC_STATE_EXPUNGE_MESSAGES:  return "SYNC_STATE_EXPUNGE_MESSAGES";
00488   case SYNC_STATE_HANDLE_INBOX:      return "SYNC_STATE_HANDLE_INBOX";
00489   case SYNC_STATE_GET_USERRIGHTS:    return "SYNC_STATE_GET_USERRIGHTS";
00490   case SYNC_STATE_GET_ACLS:          return "SYNC_STATE_GET_ACLS";
00491   case SYNC_STATE_SET_ACLS:          return "SYNC_STATE_SET_ACLS";
00492   case SYNC_STATE_FIND_SUBFOLDERS:   return "SYNC_STATE_FIND_SUBFOLDERS";
00493   case SYNC_STATE_SYNC_SUBFOLDERS:   return "SYNC_STATE_SYNC_SUBFOLDERS";
00494   case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY";
00495   case SYNC_STATE_RENAME_FOLDER:     return "SYNC_STATE_RENAME_FOLDER";
00496   default:                           return "Unknown state";
00497   }
00498 }
00499 
00500 /*
00501   Progress calculation: each step is assigned a span. Initially the total is 100.
00502   But if we skip a step, don't increase the progress.
00503   This leaves more room for the step a with variable size (get_messages)
00504    connecting 5
00505    getuserrights 5
00506    rename 5
00507    check_uidvalidity 5
00508    create_subfolders 5
00509    put_messages 10 (but it can take a very long time, with many messages....)
00510    upload_flags 5
00511    list_subfolders 5
00512    list_subfolders2 0 (all local)
00513    delete_subfolders 5
00514    list_messages 10
00515    delete_messages 10
00516    expunge_messages 5
00517    get_messages variable (remaining-5) i.e. minimum 15.
00518    set_acls 0 (rare)
00519    get_acls 5
00520 
00521   noContent folders have only a few of the above steps
00522   (permissions, and all subfolder stuff), so its steps should be given more span
00523 
00524  */
00525 
00526 // While the server synchronization is running, mSyncState will hold
00527 // the state that should be executed next
00528 void KMFolderCachedImap::serverSyncInternal()
00529 {
00530   // This is used to stop processing when we're about to exit
00531   // and the current job wasn't cancellable.
00532   // For user-requested abort, we'll use signalAbortRequested instead.
00533   if( kmkernel->mailCheckAborted() ) {
00534     resetSyncState();
00535     emit folderComplete( this, false );
00536     return;
00537   }
00538 
00539   //kdDebug(5006) << label() << ": " << state2String( mSyncState ) << endl;
00540   switch( mSyncState ) {
00541   case SYNC_STATE_INITIAL:
00542   {
00543     mProgress = 0;
00544     newState( mProgress, i18n("Synchronizing"));
00545 
00546     open();
00547     if ( !noContent() )
00548         mAccount->addLastUnreadMsgCount( this, countUnread() );
00549 
00550     // Connect to the server (i.e. prepare the slave)
00551     ImapAccountBase::ConnectionState cs = mAccount->makeConnection();
00552     if ( cs == ImapAccountBase::Error ) {
00553       // Cancelled by user, or slave can't start
00554       // kdDebug(5006) << "makeConnection said Error, aborting." << endl;
00555       // We stop here. We're already in SYNC_STATE_INITIAL for the next time.
00556       newState( mProgress, i18n( "Error connecting to server %1" ).arg( mAccount->host() ) );
00557       close();
00558       emit folderComplete(this, FALSE);
00559       break;
00560     } else if ( cs == ImapAccountBase::Connecting ) {
00561       // kdDebug(5006) << "makeConnection said Connecting, waiting for signal." << endl;
00562       newState( mProgress, i18n("Connecting to %1").arg( mAccount->host() ) );
00563       // We'll wait for the connectionResult signal from the account.
00564       connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
00565                this, SLOT( slotConnectionResult(int, const QString&) ) );
00566       break;
00567     } else {
00568       // Connected
00569       // kdDebug(5006) << "makeConnection said Connected, proceeding." << endl;
00570       mSyncState = SYNC_STATE_GET_USERRIGHTS;
00571       // Fall through to next state
00572     }
00573   }
00574 
00575   case SYNC_STATE_GET_USERRIGHTS:
00576     mSyncState = SYNC_STATE_RENAME_FOLDER;
00577 
00578     if( !noContent() && mAccount->hasACLSupport() ) {
00579       // Check the user's own rights. We do this every time in case they changed.
00580       newState( mProgress, i18n("Checking permissions"));
00581       connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
00582                this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
00583       mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case
00584       break;
00585     }
00586 
00587   case SYNC_STATE_RENAME_FOLDER:
00588   {
00589     mSyncState = SYNC_STATE_CHECK_UIDVALIDITY;
00590     // Returns the new name if the folder was renamed, empty otherwise.
00591     QString newName = mAccount->renamedFolder( imapPath() );
00592     if ( !newName.isEmpty() ) {
00593       newState( mProgress, i18n("Renaming folder") );
00594       CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this );
00595       connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
00596       connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00597       job->start();
00598       break;
00599     }
00600   }
00601 
00602   case SYNC_STATE_CHECK_UIDVALIDITY:
00603     mSyncState = SYNC_STATE_CREATE_SUBFOLDERS;
00604     if( !noContent() ) {
00605       checkUidValidity();
00606       break;
00607     }
00608     // Else carry on
00609 
00610   case SYNC_STATE_CREATE_SUBFOLDERS:
00611     mSyncState = SYNC_STATE_PUT_MESSAGES;
00612     createNewFolders();
00613     break;
00614 
00615   case SYNC_STATE_PUT_MESSAGES:
00616     mSyncState = SYNC_STATE_UPLOAD_FLAGS;
00617     if( !noContent() ) {
00618       uploadNewMessages();
00619       break;
00620     }
00621     // Else carry on
00622   case SYNC_STATE_UPLOAD_FLAGS:
00623     mSyncState = SYNC_STATE_LIST_SUBFOLDERS;
00624     if( !noContent() ) {
00625        // We haven't downloaded messages yet, so we need to build the map.
00626        if( uidMapDirty )
00627          reloadUidMap();
00628        // Upload flags, unless we know from the ACL that we're not allowed
00629        // to do that or they did not change locally
00630        if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::WriteFlags ) ) {
00631          if ( mStatusChangedLocally ) {
00632            uploadFlags();
00633            break;
00634          } else {
00635            kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl;
00636          }
00637        }
00638     }
00639     // Else carry on
00640   case SYNC_STATE_LIST_SUBFOLDERS:
00641     mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
00642     newState( mProgress, i18n("Retrieving folderlist"));
00643     if( !listDirectory() ) {
00644       mSyncState = SYNC_STATE_INITIAL;
00645       KMessageBox::error(0, i18n("Error while retrieving the folderlist"));
00646     }
00647     break;
00648 
00649   case SYNC_STATE_LIST_SUBFOLDERS2:
00650     mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
00651     mProgress += 10;
00652     newState( mProgress, i18n("Retrieving subfolders"));
00653     listDirectory2();
00654     break;
00655 
00656   case SYNC_STATE_DELETE_SUBFOLDERS:
00657     mSyncState = SYNC_STATE_LIST_MESSAGES;
00658     if( !foldersForDeletionOnServer.isEmpty() ) {
00659       newState( mProgress, i18n("Deleting folders from server"));
00660       CachedImapJob* job = new CachedImapJob( foldersForDeletionOnServer,
00661                                                   CachedImapJob::tDeleteFolders, this );
00662       connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
00663       connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00664       job->start();
00665       break;
00666     }
00667     // Not needed, the next step emits newState very quick
00668     //newState( mProgress, i18n("No folders to delete from server"));
00669       // Carry on
00670 
00671   case SYNC_STATE_LIST_MESSAGES:
00672     mSyncState = SYNC_STATE_DELETE_MESSAGES;
00673     if( !noContent() ) {
00674       newState( mProgress, i18n("Retrieving message list"));
00675       listMessages();
00676       break;
00677     }
00678     // Else carry on
00679 
00680   case SYNC_STATE_DELETE_MESSAGES:
00681     mSyncState = SYNC_STATE_EXPUNGE_MESSAGES;
00682     if( !noContent() ) {
00683       if( deleteMessages() ) {
00684         // Fine, we will continue with the next state
00685       } else {
00686         // No messages to delete, skip to GET_MESSAGES
00687         newState( mProgress, i18n("No messages to delete..."));
00688         mSyncState = SYNC_STATE_GET_MESSAGES;
00689         serverSyncInternal();
00690       }
00691       break;
00692     }
00693     // Else carry on
00694 
00695   case SYNC_STATE_EXPUNGE_MESSAGES:
00696     mSyncState = SYNC_STATE_GET_MESSAGES;
00697     if( !noContent() ) {
00698       newState( mProgress, i18n("Expunging deleted messages"));
00699       CachedImapJob *job = new CachedImapJob( QString::null,
00700                                               CachedImapJob::tExpungeFolder, this );
00701       connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
00702       connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00703       job->start();
00704       break;
00705     }
00706     // Else carry on
00707 
00708   case SYNC_STATE_GET_MESSAGES:
00709     mSyncState = SYNC_STATE_HANDLE_INBOX;
00710     if( !noContent() ) {
00711       if( !mMsgsForDownload.isEmpty() ) {
00712         newState( mProgress, i18n("Retrieving new messages"));
00713         CachedImapJob *job = new CachedImapJob( mMsgsForDownload,
00714                                                 CachedImapJob::tGetMessage,
00715                                                 this );
00716         connect( job, SIGNAL( progress(unsigned long, unsigned long) ),
00717                  this, SLOT( slotProgress(unsigned long, unsigned long) ) );
00718         connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) );
00719         connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00720         job->start();
00721         mMsgsForDownload.clear();
00722         break;
00723       } else {
00724         newState( mProgress, i18n("No new messages from server"));
00725         /* There were no messages to download, but it could be that we uploaded some
00726            which we didn't need to download again because we already knew the uid.
00727            Now that we are sure there is nothing to download, and everything that had
00728            to be deleted on the server has been deleted, adjust our local notion of the
00729            highes uid seen thus far. */
00730         slotUpdateLastUid();
00731         if( mLastUid == 0 && uidWriteTimer == -1 )
00732           // This is probably a new and empty folder. Write the UID cache
00733           writeUidCache();
00734       }
00735     }
00736 
00737     // Else carry on
00738 
00739   case SYNC_STATE_HANDLE_INBOX:
00740     // Wrap up the 'download emails' stage. We always end up at 95 here.
00741     mProgress = 95;
00742 
00743     // Continue with the ACLs
00744     mSyncState = SYNC_STATE_SET_ACLS;
00745 
00746   case SYNC_STATE_SET_ACLS:
00747     mSyncState = SYNC_STATE_GET_ACLS;
00748 
00749     if( !noContent() && mAccount->hasACLSupport() ) {
00750       bool hasChangedACLs = false;
00751       ACLList::ConstIterator it = mACLList.begin();
00752       for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) {
00753         hasChangedACLs = (*it).changed;
00754       }
00755       if ( hasChangedACLs ) {
00756         newState( mProgress, i18n("Setting permissions"));
00757         KURL url = mAccount->getUrl();
00758         url.setPath( imapPath() );
00759         KIO::Job* job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url, mACLList );
00760         ImapAccountBase::jobData jd( url.url(), folder() );
00761         mAccount->insertJob(job, jd);
00762 
00763         connect(job, SIGNAL(result(KIO::Job *)),
00764                 SLOT(slotMultiSetACLResult(KIO::Job *)));
00765         connect(job, SIGNAL(aclChanged( const QString&, int )),
00766                 SLOT(slotACLChanged( const QString&, int )) );
00767         break;
00768       }
00769     }
00770 
00771   case SYNC_STATE_GET_ACLS:
00772     // Continue with the subfolders
00773     mSyncState = SYNC_STATE_FIND_SUBFOLDERS;
00774 
00775     if( !noContent() && mAccount->hasACLSupport() ) {
00776       newState( mProgress, i18n( "Retrieving permissions" ) );
00777       mAccount->getACL( folder(), mImapPath );
00778       connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
00779                this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
00780       break;
00781     }
00782 
00783   case SYNC_STATE_FIND_SUBFOLDERS:
00784     {
00785       mProgress = 98;
00786       newState( mProgress, i18n("Updating cache file"));
00787 
00788       mSyncState = SYNC_STATE_SYNC_SUBFOLDERS;
00789       mSubfoldersForSync.clear();
00790       mCurrentSubfolder = 0;
00791       if( folder() && folder()->child() ) {
00792         KMFolderNode *node = folder()->child()->first();
00793         while( node ) {
00794           if( !node->isDir() ) {
00795             KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
00796             // Only sync folders that have been accepted by the server
00797             if ( !storage->imapPath().isEmpty()
00798                  // and that were not just deleted from it
00799                  && !foldersForDeletionOnServer.contains( storage->imapPath() ) )
00800               mSubfoldersForSync << storage;
00801           }
00802           node = folder()->child()->next();
00803         }
00804       }
00805     }
00806 
00807     // All done for this folder.
00808     mProgress = 100; // all done
00809     newState( mProgress, i18n("Synchronization done"));
00810 
00811     if ( !mRecurse ) // "check mail for this folder" only
00812       mSubfoldersForSync.clear();
00813 
00814     // Carry on
00815   case SYNC_STATE_SYNC_SUBFOLDERS:
00816     {
00817       if( mCurrentSubfolder ) {
00818         disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
00819                     this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
00820         mCurrentSubfolder = 0;
00821       }
00822 
00823       if( mSubfoldersForSync.isEmpty() ) {
00824         mSyncState = SYNC_STATE_INITIAL;
00825         mAccount->addUnreadMsgCount( this, countUnread() ); // before closing
00826         close();
00827         emit folderComplete( this, TRUE );
00828       } else {
00829         mCurrentSubfolder = mSubfoldersForSync.front();
00830         mSubfoldersForSync.pop_front();
00831         connect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
00832                  this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
00833 
00834         // kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl;
00835         assert( !mCurrentSubfolder->imapPath().isEmpty() );
00836         mCurrentSubfolder->setAccount( account() );
00837         mCurrentSubfolder->serverSync( mRecurse /*which is true*/ );
00838       }
00839     }
00840     break;
00841 
00842   default:
00843     kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state "
00844               << mSyncState << endl;
00845   }
00846 }
00847 
00848 /* Connected to the imap account's connectionResult signal.
00849    Emitted when the slave connected or failed to connect.
00850 */
00851 void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString& errorMsg )
00852 {
00853   disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
00854               this, SLOT( slotConnectionResult(int, const QString&) ) );
00855   if ( !errorCode ) {
00856     // Success
00857     mSyncState = SYNC_STATE_GET_USERRIGHTS;
00858     mProgress += 5;
00859     serverSyncInternal();
00860   } else {
00861     // Error (error message already shown by the account)
00862     newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ));
00863     emit folderComplete(this, FALSE);
00864   }
00865 }
00866 
00867 /* find new messages (messages without a UID) */
00868 QValueList<unsigned long> KMFolderCachedImap::findNewMessages()
00869 {
00870   QValueList<unsigned long> result;
00871   for( int i = 0; i < count(); ++i ) {
00872     KMMsgBase *msg = getMsgBase( i );
00873     if( !msg ) continue; /* what goes on if getMsg() returns 0? */
00874     if ( msg->UID() == 0 )
00875       result.append( msg->getMsgSerNum() );
00876   }
00877   return result;
00878 }
00879 
00880 /* Upload new messages to server */
00881 void KMFolderCachedImap::uploadNewMessages()
00882 {
00883   QValueList<unsigned long> newMsgs = findNewMessages();
00884   if( !newMsgs.isEmpty() ) {
00885 
00886     newState( mProgress, i18n("Uploading messages to server"));
00887     CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this );
00888     connect( job, SIGNAL( progress( unsigned long, unsigned long) ),
00889              this, SLOT( slotPutProgress(unsigned long, unsigned long) ) );
00890     connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00891     job->start();
00892   } else {
00893     newState( mProgress, i18n("No messages to upload to server"));
00894 
00895     serverSyncInternal();
00896   }
00897 }
00898 
00899 /* Progress info during uploadNewMessages */
00900 void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total )
00901 {
00902   // (going from mProgress to mProgress+10)
00903   int progressSpan = 10;
00904   newState( mProgress + (progressSpan * done) / total, QString::null );
00905   if ( done == total ) // we're done
00906     mProgress += progressSpan;
00907 }
00908 
00909 /* Upload message flags to server */
00910 void KMFolderCachedImap::uploadFlags()
00911 {
00912   if ( !uidMap.isEmpty() ) {
00913     mStatusFlagsJobs = 0;
00914     newState( mProgress, i18n("Uploading status of messages to server"));
00915 
00916     // FIXME DUPLICATED FROM KMFOLDERIMAP
00917     QMap< QString, QStringList > groups;
00918     //open(); //already done
00919     for( int i = 0; i < count(); ++i ) {
00920       KMMsgBase* msg = getMsgBase( i );
00921       if( !msg || msg->UID() == 0 )
00922         // Either not a valid message or not one that is on the server yet
00923         continue;
00924 
00925       QString flags = KMFolderImap::statusToFlags(msg->status());
00926       // Collect uids for each typem of flags.
00927       QString uid;
00928       uid.setNum( msg->UID() );
00929       groups[flags].append(uid);
00930     }
00931     QMapIterator< QString, QStringList > dit;
00932     for( dit = groups.begin(); dit != groups.end(); ++dit ) {
00933       QCString flags = dit.key().latin1();
00934       QStringList sets = KMFolderImap::makeSets( (*dit), true );
00935       mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap....
00936       // Send off a status setting job for each set.
00937       for( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
00938         QString imappath = imapPath() + ";UID=" + ( *slit );
00939         mAccount->setImapStatus(folder(), imappath, flags);
00940       }
00941     }
00942     // FIXME END DUPLICATED FROM KMFOLDERIMAP
00943 
00944     if ( mStatusFlagsJobs ) {
00945       connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
00946                this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
00947       return;
00948     }
00949   }
00950   newState( mProgress, i18n("No messages to upload to server"));
00951   serverSyncInternal();
00952 }
00953 
00954 void KMFolderCachedImap::slotImapStatusChanged(KMFolder* folder, const QString&, bool cont)
00955 {
00956   if ( folder->storage() == this ) {
00957     --mStatusFlagsJobs;
00958     if ( mStatusFlagsJobs == 0 || !cont ) // done or aborting
00959       disconnect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
00960                   this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
00961     if ( mStatusFlagsJobs == 0 && cont ) {
00962       mProgress += 5;
00963       serverSyncInternal();
00964     }
00965   }
00966 }
00967 
00968 
00969 void KMFolderCachedImap::setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle)
00970 {
00971   KMFolderMaildir::setStatus(ids, status, toggle);
00972   // This is not perfect, what if the status didn't really change? Oh well ...
00973   mStatusChangedLocally = true;
00974 }
00975 
00976 /* Upload new folders to server */
00977 void KMFolderCachedImap::createNewFolders()
00978 {
00979   QValueList<KMFolderCachedImap*> newFolders = findNewFolders();
00980   //kdDebug(5006) << label() << " createNewFolders:" << newFolders.count() << " new folders." << endl;
00981   if( !newFolders.isEmpty() ) {
00982     newState( mProgress, i18n("Creating subfolders on server"));
00983     CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this );
00984     connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
00985     connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
00986     job->start();
00987   } else {
00988     serverSyncInternal();
00989   }
00990 }
00991 
00992 QValueList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders()
00993 {
00994   QValueList<KMFolderCachedImap*> newFolders;
00995   if( folder() && folder()->child() ) {
00996     KMFolderNode *node = folder()->child()->first();
00997     while( node ) {
00998       if( !node->isDir() ) {
00999         if( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) {
01000           kdError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! "
01001                         << node->name() << " is not an IMAP folder\n";
01002           node = folder()->child()->next();
01003           assert(0);
01004         }
01005         KMFolderCachedImap* folder = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
01006         if( folder->imapPath().isEmpty() ) newFolders << folder;
01007       }
01008       node = folder()->child()->next();
01009     }
01010   }
01011   return newFolders;
01012 }
01013 
01014 bool KMFolderCachedImap::deleteMessages()
01015 {
01016   /* Delete messages from cache that are gone from the server */
01017   QPtrList<KMMessage> msgsForDeletion;
01018 
01019   // It is not possible to just go over all indices and remove
01020   // them one by one because the index list can get resized under
01021   // us. So use msg pointers instead
01022 
01023   QMap<ulong,int>::const_iterator it = uidMap.constBegin();
01024   for( ; it != uidMap.end(); it++ ) {
01025     ulong uid ( it.key() );
01026     if( uid!=0 && !uidsOnServer.find( uid ) )
01027       msgsForDeletion.append( getMsg( *it ) );
01028   }
01029 
01030   if( !msgsForDeletion.isEmpty() ) {
01031     removeMsg( msgsForDeletion );
01032   }
01033 
01034   /* Delete messages from the server that we dont have anymore */
01035   if( !uidsForDeletionOnServer.isEmpty() ) {
01036     newState( mProgress, i18n("Deleting removed messages from server"));
01037     QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true );
01038     uidsForDeletionOnServer.clear();
01039     kdDebug(5006) << "Deleting " << sets.count() << " sets of messages from server folder " << imapPath() << endl;
01040     CachedImapJob *job = new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this );
01041     connect( job, SIGNAL( result(KMail::FolderJob *) ),
01042              this, SLOT( slotDeleteMessagesResult(KMail::FolderJob *) ) );
01043     job->start();
01044     return true;
01045   } else {
01046     return false;
01047   }
01048 }
01049 
01050 void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob* job )
01051 {
01052   if ( job->error() ) {
01053     // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages
01054     mSyncState = SYNC_STATE_GET_MESSAGES;
01055   }
01056   mProgress += 10;
01057   serverSyncInternal();
01058 }
01059 
01060 void KMFolderCachedImap::checkUidValidity() {
01061   // IMAP root folders don't seem to have a UID validity setting.
01062   // Also, don't try the uid validity on new folders
01063   if( imapPath().isEmpty() || imapPath() == "/" )
01064     // Just proceed
01065     serverSyncInternal();
01066   else {
01067     newState( mProgress, i18n("Checking folder validity"));
01068     CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this );
01069     connect( job, SIGNAL( result( KMail::FolderJob* ) ),
01070              this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) );
01071     job->start();
01072   }
01073 }
01074 
01075 void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob* job )
01076 {
01077   if ( job->error() ) { // there was an error and the user chose "continue"
01078     // We can't continue doing anything in the same folder though, it would delete all mails.
01079     // But we can continue to subfolders if any. Well we can also try annotation/acl stuff...
01080     mSyncState = SYNC_STATE_HANDLE_INBOX;
01081   }
01082   mProgress += 5;
01083   serverSyncInternal();
01084 }
01085 
01086 /* This will only list the messages in a folder.
01087    No directory listing done*/
01088 void KMFolderCachedImap::listMessages() {
01089   if( imapPath() == "/" ) {
01090     // Don't list messages on the root folder
01091     serverSyncInternal();
01092     return;
01093   }
01094 
01095   if( !mAccount->slave() ) { // sync aborted
01096     resetSyncState();
01097     emit folderComplete( this, false );
01098     return;
01099   }
01100   uidsOnServer.clear();
01101   uidsOnServer.resize( count() * 2 );
01102   uidsForDeletionOnServer.clear();
01103   mMsgsForDownload.clear();
01104   mUidsForDownload.clear();
01105 
01106   CachedImapJob* job = new CachedImapJob( FolderJob::tListMessages, this );
01107   connect( job, SIGNAL( result(KMail::FolderJob *) ),
01108            this, SLOT( slotGetLastMessagesResult(KMail::FolderJob *) ) );
01109   job->start();
01110 }
01111 
01112 void KMFolderCachedImap::slotGetLastMessagesResult(KMail::FolderJob *job)
01113 {
01114   getMessagesResult(job, true);
01115 }
01116 
01117 // Connected to the listMessages job in CachedImapJob
01118 void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const QByteArray & data)
01119 {
01120   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
01121   if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
01122     kdDebug(5006) << "could not find job!?!?!" << endl;
01123     serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */
01124     return;
01125   }
01126   (*it).cdata += QCString(data, data.size() + 1);
01127   int pos = (*it).cdata.find("\r\n--IMAPDIGEST");
01128   if (pos > 0) {
01129     int a = (*it).cdata.find("\r\nX-uidValidity:");
01130     if (a != -1) {
01131       int b = (*it).cdata.find("\r\n", a + 17);
01132       setUidValidity((*it).cdata.mid(a + 17, b - a - 17));
01133     }
01134     a = (*it).cdata.find("\r\nX-Access:");
01135     if (a != -1) {
01136       int b = (*it).cdata.find("\r\n", a + 12);
01137       QString access = (*it).cdata.mid(a + 12, b - a - 12);
01138       mReadOnly = access == "Read only";
01139     }
01140     (*it).cdata.remove(0, pos);
01141   }
01142   pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
01143   // Start with something largish when rebuilding the cache
01144   if ( uidsOnServer.size() == 0 )
01145     uidsOnServer.resize( KMail::nextPrime( 2000 ) );
01146   int flags;
01147   const int v = 42;
01148   while (pos >= 0) {
01149     KMMessage msg;
01150     msg.fromString((*it).cdata.mid(16, pos - 16));
01151     flags = msg.headerField("X-Flags").toInt();
01152     bool deleted = ( flags & 8 );
01153     ulong uid = msg.UID();
01154     if ( !deleted ) {
01155       if( uid != 0 ) {
01156         if ( uidsOnServer.count() == uidsOnServer.size() ) {
01157           uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) );
01158           kdDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl;
01159         }
01160         uidsOnServer.insert( uid, &v );
01161       }
01162       if (  uid <= lastUid() ) {
01163        /*
01164         * If this message UID is not present locally, then it must
01165         * have been deleted by the user, so we delete it on the
01166         * server also.
01167         *
01168         * This relies heavily on lastUid() being correct at all times.
01169         */
01170         // kdDebug(5006) << "KMFolderCachedImap::slotGetMessagesData() : folder "<<label()<<" already has msg="<<msg->headerField("Subject") << ", UID="<<uid << ", lastUid = " << mLastUid << endl;
01171         KMMsgBase *existingMessage = findByUID(uid);
01172           // if this is a read only folder, ignore status updates from the server
01173           // since we can't write our status back our local version is what has to
01174           // be considered correct.
01175         if( !existingMessage ) {
01176           // kdDebug(5006) << "message with uid " << uid << " is gone from local cache. Must be deleted on server!!!" << endl;
01177           uidsForDeletionOnServer << uid;
01178         } else {
01179           if (!mReadOnly) {
01180             /* The message is OK, update flags */
01181             KMFolderImap::flagsToStatus( existingMessage, flags );
01182           }
01183         }
01184         // kdDebug(5006) << "message with uid " << uid << " found in the local cache. " << endl;
01185       } else {
01186         // The message is new since the last sync, but we might have just uploaded it, in which case
01187         // the uid map already contains it.
01188         if ( !uidMap.contains( uid ) ) {
01189           ulong size = msg.headerField("X-Length").toULong();
01190           mMsgsForDownload << KMail::CachedImapJob::MsgForDownload(uid, flags, size);
01191           if( imapPath() == "/INBOX/" )
01192             mUidsForDownload << uid;
01193         }
01194         // Remember the highest uid and once the download is completed, update mLastUid
01195         if ( uid > mTentativeHighestUid )
01196           mTentativeHighestUid = uid;
01197       }
01198     }
01199     (*it).cdata.remove(0, pos);
01200     (*it).done++;
01201     pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
01202   }
01203 }
01204 
01205 void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet )
01206 {
01207   mProgress += 10;
01208   if( job->error() ) { // error listing messages but the user chose to continue
01209     mContentState = imapNoInformation;
01210   } else {
01211     if( lastSet ) { // always true here (this comes from online-imap...)
01212       mContentState = imapFinished;
01213       mStatusChangedLocally = false; // we are up to date again
01214     }
01215   }
01216   serverSyncInternal();
01217 }
01218 
01219 void KMFolderCachedImap::slotProgress(unsigned long done, unsigned long total)
01220 {
01221   int progressSpan = 100 - 5 - mProgress;
01222   //kdDebug(5006) << "KMFolderCachedImap::slotProgress done=" << done << " total=" << total << "=> mProgress=" << mProgress + ( progressSpan * done ) / total << endl;
01223   // Progress info while retrieving new emails
01224   // (going from mProgress to mProgress+progressSpan)
01225   newState( mProgress + (progressSpan * done) / total, QString::null );
01226 }
01227 
01228 
01229 void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount)
01230 {
01231   assert( aAccount->isA("KMAcctCachedImap") );
01232   mAccount = aAccount;
01233   if( imapPath()=="/" ) aAccount->setFolder( folder() );
01234 
01235   // Folder was renamed in a previous session, and the user didn't sync yet
01236   QString newName = mAccount->renamedFolder( imapPath() );
01237   if ( !newName.isEmpty() )
01238     folder()->setLabel( newName );
01239 
01240   if( !folder() || !folder()->child() || !folder()->child()->count() ) return;
01241   for( KMFolderNode* node = folder()->child()->first(); node;
01242        node = folder()->child()->next() )
01243     if (!node->isDir())
01244       static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage())->setAccount(aAccount);
01245 }
01246 
01247 
01248 // This lists the subfolders on the server
01249 // and (in slotListResult) takes care of folders that have been removed on the server
01250 bool KMFolderCachedImap::listDirectory(bool secondStep)
01251 {
01252   mSubfolderState = imapInProgress;
01253   if( !mAccount->slave() ) { // sync aborted
01254     resetSyncState();
01255     emit folderComplete( this, false );
01256     return false;
01257   }
01258   // reset
01259   if ( this == mAccount->rootFolder() )
01260     mAccount->setHasInbox( false );
01261 
01262   // get the folders
01263   ImapAccountBase::ListType type = ImapAccountBase::List;
01264   if ( mAccount->onlySubscribedFolders() )
01265     type = ImapAccountBase::ListSubscribed;
01266   ListJob* job = new ListJob( this, mAccount, type, secondStep,
01267       false, mAccount->hasInbox() );
01268   connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
01269           const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
01270       this, SLOT(slotListResult(const QStringList&, const QStringList&,
01271           const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
01272   job->start();
01273 
01274   return true;
01275 }
01276 
01277 void KMFolderCachedImap::slotListResult( const QStringList& folderNames,
01278                                          const QStringList& folderPaths,
01279                                          const QStringList& folderMimeTypes,
01280                                          const QStringList& folderAttributes,
01281                                          const ImapAccountBase::jobData& jobData )
01282 {
01283   //kdDebug(5006) << label() << ": folderNames=" << folderNames << " folderPaths=" << folderPaths << " mimeTypes=" << folderMimeTypes << endl;
01284   mSubfolderNames = folderNames;
01285   mSubfolderPaths = folderPaths;
01286   mSubfolderMimeTypes = folderMimeTypes;
01287   mSubfolderAttributes = folderAttributes;
01288 
01289   mSubfolderState = imapFinished;
01290   bool it_inboxOnly = jobData.inboxOnly;
01291   // pass it to listDirectory2
01292   mCreateInbox = jobData.createInbox;
01293 
01294   if (it_inboxOnly) {
01295     // list again only for the INBOX
01296     listDirectory(TRUE);
01297     return;
01298   }
01299 
01300   if ( folder()->isSystemFolder() && mImapPath == "/INBOX/"
01301        && mAccount->prefix() == "/INBOX/" )
01302   {
01303     // do not create folders under INBOX
01304     mCreateInbox = false;
01305     mSubfolderNames.clear();
01306   }
01307   folder()->createChildFolder();
01308   // Find all subfolders present on disk but not on the server
01309   KMFolderNode *node = folder()->child()->first();
01310   QPtrList<KMFolder> toRemove;
01311   while (node) {
01312     if (!node->isDir() ) {
01313       KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
01314       if ( mSubfolderNames.findIndex(node->name()) == -1 &&
01315           (node->name().upper() != "INBOX" || !mCreateInbox) )
01316       {
01317         // This subfolder isn't present on the server
01318         if( !f->imapPath().isEmpty()  ) {
01319           // The folder has an imap path set, so it has been
01320           // on the server before. Delete it locally.
01321           toRemove.append( f->folder() );
01322           kdDebug(5006) << node->name() << " isn't on the server. It has an imapPath -> delete it locally" << endl;
01323         } else { // shouldn't happen
01324           kdDebug(5006) << node->name() << " isn't on the server, but has no imapPath. ERROR - why didn't createNewFolders create it?" << endl;
01325         }
01326       } else { // folder both local and on server
01327         //kdDebug(5006) << node->name() << " is on the server." << endl;
01328       }
01329     } else {
01330       //kdDebug(5006) << "skipping dir node:" << node->name() << endl;
01331     }
01332     node = folder()->child()->next();
01333   }
01334 
01335   for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() )
01336     kmkernel->dimapFolderMgr()->remove( doomed );
01337 
01338   mProgress += 5;
01339   serverSyncInternal();
01340 }
01341 
01342 // This synchronizes the local folders as needed (creation/deletion). No network communication here.
01343 void KMFolderCachedImap::listDirectory2() {
01344   foldersForDeletionOnServer.clear();
01345   QString path = folder()->path();
01346   KMFolderCachedImap *f = 0;
01347   kmkernel->dimapFolderMgr()->quiet(true);
01348 
01349   if (mCreateInbox)
01350   {
01351     KMFolderNode *node;
01352     // create the INBOX
01353     for (node = folder()->child()->first(); node; node = folder()->child()->next())
01354       if (!node->isDir() && node->name() == "INBOX") break;
01355     if (node)
01356       f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
01357     else {
01358       KMFolder* newFolder = folder()->child()->createFolder("INBOX", true, KMFolderTypeCachedImap);
01359       if (newFolder)
01360         f = static_cast<KMFolderCachedImap*>(newFolder->storage());
01361     }
01362     f->setAccount(mAccount);
01363     f->setImapPath("/INBOX/");
01364     f->folder()->setLabel(i18n("inbox"));
01365     if (!node) {
01366       f->close();
01367       kmkernel->dimapFolderMgr()->contentsChanged();
01368     }
01369     // so we have an INBOX
01370     mAccount->setHasInbox( true );
01371   }
01372 
01373   // Find all subfolders present on server but not on disk
01374   for (uint i = 0; i < mSubfolderNames.count(); i++) {
01375 
01376     if (mSubfolderNames[i].upper() == "INBOX" &&
01377         mSubfolderPaths[i] == "/INBOX/" &&
01378         mAccount->hasInbox()) // do not create an additional inbox
01379       continue;
01380 
01381     // Find the subdir, if already present
01382     KMFolderNode *node;
01383     for (node = folder()->child()->first(); node;
01384          node = folder()->child()->next())
01385       if (!node->isDir() && node->name() == mSubfolderNames[i]) break;
01386 
01387     if (!node) {
01388       // This folder is not present here
01389       // Either it's new on the server, or we just deleted it.
01390       QString subfolderPath = mSubfolderPaths[i];
01391       // The code used to look at the uidcache to know if it was "just deleted".
01392       // But this breaks with noContent folders and with shared folders.
01393       // So instead we keep a list in the account.
01394       bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath );
01395       // That list is saved/restored across sessions, but to avoid any mistake,
01396       // ask for confirmation if the folder was deleted in a previous session
01397       // (could be that the folder was deleted & recreated meanwhile from another client...)
01398       if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) {
01399            locallyDeleted = KMessageBox::warningYesNo(
01400              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] ) ) == KMessageBox::Yes;
01401       }
01402 
01403       if ( locallyDeleted ) {
01404         kdDebug(5006) << subfolderPath << " was deleted locally => delete on server." << endl;
01405         foldersForDeletionOnServer << subfolderPath;
01406       } else {
01407         kdDebug(5006) << subfolderPath << " is a new folder on the server => create local cache" << endl;
01408         KMFolder* newFolder = folder()->child()->createFolder(mSubfolderNames[i], false, KMFolderTypeCachedImap);
01409         if (newFolder)
01410           f = static_cast<KMFolderCachedImap*>(newFolder->storage());
01411         if (f) {
01412           f->close();
01413           f->setAccount(mAccount);
01414           kmkernel->dimapFolderMgr()->contentsChanged();
01415         } else {
01416           kdDebug(5006) << "can't create folder " << mSubfolderNames[i] <<endl;
01417         }
01418       }
01419     } else { // Folder found locally
01420       if( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap )
01421         f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
01422     }
01423 
01424     if( f ) {
01425       // kdDebug(5006) << "folder("<<f->name()<<")->imapPath()=" << f->imapPath()
01426       //               << "\nSetting imapPath " << mSubfolderPaths[i] << endl;
01427       // Write folder settings
01428       f->setAccount(mAccount);
01429       f->setNoContent(mSubfolderMimeTypes[i] == "inode/directory");
01430       f->setNoChildren(mSubfolderMimeTypes[i] == "message/digest");
01431       f->setImapPath(mSubfolderPaths[i]);
01432     }
01433   }
01434   kmkernel->dimapFolderMgr()->quiet(false);
01435   emit listComplete(this);
01436   serverSyncInternal();
01437 }
01438 
01439 void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool success)
01440 {
01441   Q_UNUSED(sub);
01442   //kdDebug(5006) << label() << " slotSubFolderComplete: " << sub->label() << endl;
01443   if ( success ) {
01444     serverSyncInternal();
01445   }
01446   else
01447   {
01448     // success == false means the sync was aborted.
01449     if ( mCurrentSubfolder ) {
01450       Q_ASSERT( sub == mCurrentSubfolder );
01451       disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
01452                   this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
01453       mCurrentSubfolder = 0;
01454     }
01455 
01456     mSubfoldersForSync.clear();
01457     mSyncState = SYNC_STATE_INITIAL;
01458     close();
01459     emit folderComplete( this, false );
01460   }
01461 }
01462 
01463 void KMFolderCachedImap::slotSimpleData(KIO::Job * job, const QByteArray & data)
01464 {
01465   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
01466   if (it == mAccount->jobsEnd()) return;
01467   QBuffer buff((*it).data);
01468   buff.open(IO_WriteOnly | IO_Append);
01469   buff.writeBlock(data.data(), data.size());
01470   buff.close();
01471 }
01472 
01473 
01474 FolderJob*
01475 KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
01476                                  QString, const AttachmentStrategy* ) const
01477 {
01478   QPtrList<KMMessage> msgList;
01479   msgList.append( msg );
01480   CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
01481   job->setParentFolder( this );
01482   return job;
01483 }
01484 
01485 FolderJob*
01486 KMFolderCachedImap::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
01487                                  FolderJob::JobType jt, KMFolder *folder ) const
01488 {
01489   //FIXME: how to handle sets here?
01490   Q_UNUSED( sets );
01491   CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
01492   job->setParentFolder( this );
01493   return job;
01494 }
01495 
01496 void
01497 KMFolderCachedImap::setUserRights( unsigned int userRights )
01498 {
01499   mUserRights = userRights;
01500 }
01501 
01502 void
01503 KMFolderCachedImap::slotReceivedUserRights( KMFolder* folder )
01504 {
01505   if ( folder->storage() == this ) {
01506     disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
01507                 this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
01508     if ( mUserRights == 0 ) // didn't work
01509       mUserRights = -1; // error code (used in folderdia)
01510     else
01511       mReadOnly = ( mUserRights & KMail::ACLJobs::Insert ) == 0;
01512     mProgress += 5;
01513     serverSyncInternal();
01514   }
01515 }
01516 
01517 void
01518 KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job*, const KMail::ACLList& aclList )
01519 {
01520   if ( folder->storage() == this ) {
01521     disconnect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
01522                 this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
01523     mACLList = aclList;
01524     serverSyncInternal();
01525   }
01526 }
01527 
01528 void
01529 KMFolderCachedImap::setACLList( const ACLList& arr )
01530 {
01531   mACLList = arr;
01532 }
01533 
01534 void
01535 KMFolderCachedImap::slotMultiSetACLResult(KIO::Job *job)
01536 {
01537   KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
01538   if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
01539   if ( (*it).parent != folder() ) return; // Shouldn't happen
01540 
01541   if ( job->error() )
01542     // Display error but don't abort the sync just for this
01543     // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel
01544     job->showErrorDialog();
01545 
01546   if (mAccount->slave()) mAccount->removeJob(job);
01547   serverSyncInternal();
01548 }
01549 
01550 void
01551 KMFolderCachedImap::slotACLChanged( const QString& userId, int permissions )
01552 {
01553   // The job indicates success in changing the permissions for this user
01554   // -> we note that it's been done.
01555   for( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) {
01556     if ( (*it).userId == userId && (*it).permissions == permissions ) {
01557       if ( permissions == -1 ) // deleted
01558         mACLList.erase( it );
01559       else // added/modified
01560         (*it).changed = false;
01561       return;
01562     }
01563   }
01564 }
01565 
01566 // called by KMAcctCachedImap::killAllJobs
01567 void KMFolderCachedImap::resetSyncState()
01568 {
01569   mSubfoldersForSync.clear();
01570   mSyncState = SYNC_STATE_INITIAL;
01571   close();
01572   // Don't use newState here, it would revert to mProgress (which is < current value when listing messages)
01573   ProgressItem *progressItem = mAccount->mailCheckProgressItem();
01574   QString str = i18n("Aborted");
01575   if (progressItem)
01576      progressItem->setStatus( str );
01577   emit statusMsg( str );
01578 }
01579 
01580 void KMFolderCachedImap::slotIncreaseProgress()
01581 {
01582   mProgress += 5;
01583 }
01584 
01585 void KMFolderCachedImap::newState( int progress, const QString& syncStatus )
01586 {
01587   //kdDebug() << k_funcinfo << folder() << " " << mProgress << " " << syncStatus << endl;
01588   ProgressItem *progressItem = mAccount->mailCheckProgressItem();
01589   if( progressItem )
01590     progressItem->setCompletedItems( progress );
01591   if ( !syncStatus.isEmpty() ) {
01592     QString str;
01593     // For a subfolder, show the label. But for the main folder, it's already shown.
01594     if ( mAccount->imapFolder() == this )
01595       str = syncStatus;
01596     else
01597       str = QString( "%1: %2" ).arg( label() ).arg( syncStatus );
01598     if( progressItem )
01599       progressItem->setStatus( str );
01600     emit statusMsg( str );
01601   }
01602   if( progressItem )
01603     progressItem->updateProgress();
01604 }
01605 
01606 void KMFolderCachedImap::setSubfolderState( imapState state )
01607 {
01608   mSubfolderState = state;
01609   if ( state == imapNoInformation && folder()->child() )
01610   {
01611     // pass through to childs
01612     KMFolderNode* node;
01613     QPtrListIterator<KMFolderNode> it( *folder()->child() );
01614     for ( ; (node = it.current()); )
01615     {
01616       ++it;
01617       if (node->isDir()) continue;
01618       KMFolder *folder = static_cast<KMFolder*>(node);
01619       static_cast<KMFolderCachedImap*>(folder->storage())->setSubfolderState( state );
01620     }
01621   }
01622 }
01623 
01624 void KMFolderCachedImap::setImapPath(const QString &path)
01625 {
01626   mImapPath = path;
01627 }
01628 
01629 void KMFolderCachedImap::setContentsType( KMail::FolderContentsType type )
01630 {
01631   if ( type != mContentsType ) {
01632     FolderStorage::setContentsType( type );
01633     mContentsTypeChanged = true;
01634   }
01635 }
01636 
01637 void KMFolderCachedImap::slotUpdateLastUid()
01638 {
01639   if( mTentativeHighestUid != 0 )
01640     setLastUid( mTentativeHighestUid );
01641   mTentativeHighestUid = 0;
01642 }
01643 
01644 #include "kmfoldercachedimap.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Mon Apr 4 04:48:26 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003