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.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 21 19:46:48 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003