/* * kmfoldercachedimap.cpp * * Copyright (c) 2002-2004 Bo Thorsen * Copyright (c) 2002-2003 Steffen Hansen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "kmfoldercachedimap.h" #include "globalsettings.h" #include "kmkernel.h" #include "undostack.h" #include "kmfoldermgr.h" #include "kmacctcachedimap.h" #include "accountmanager.h" using KMail::AccountManager; #include "kmailicalifaceimpl.h" #include "kmfolder.h" #include "kmglobal.h" #include "acljobs.h" #include "broadcaststatus.h" using KPIM::BroadcastStatus; #include "progressmanager.h" using KMail::CachedImapJob; #include "imapaccountbase.h" using KMail::ImapAccountBase; #include "listjob.h" using KMail::ListJob; #include "folderselectiondialog.h" #include "kmcommands.h" #include "kmmainwidget.h" #include "annotationjobs.h" #include "quotajobs.h" #include "groupwareadaptor.h" using namespace KMail; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define UIDCACHE_VERSION 1 static QString incidencesForToString( KMFolderCachedImap::IncidencesFor r ) { switch ( r ) { case KMFolderCachedImap::IncForNobody: return "nobody"; case KMFolderCachedImap::IncForAdmins: return "admins"; case KMFolderCachedImap::IncForReaders: return "readers"; } return QString(); // can't happen } static KMFolderCachedImap::IncidencesFor incidencesForFromString( const QString &str ) { if ( str == "nobody" ) { return KMFolderCachedImap::IncForNobody; } if ( str == "admins" ) { return KMFolderCachedImap::IncForAdmins; } if ( str == "readers" ) { return KMFolderCachedImap::IncForReaders; } return KMFolderCachedImap::IncForAdmins; // by default } DImapTroubleShootDialog::DImapTroubleShootDialog( QWidget *parent ) : KDialog( parent ), rc( None ) { setCaption( i18n( "Troubleshooting IMAP Cache" ) ); setButtons( Ok | Cancel ); setDefaultButton( Cancel ); setModal( true ); QFrame *page = new QFrame( this ); setMainWidget( page ); QVBoxLayout *topLayout = new QVBoxLayout( page ); topLayout->setSpacing( 0 ); QString txt = i18n( "

Troubleshooting the IMAP Cache

" "

If you have problems with synchronizing an IMAP " "folder, you should first try rebuilding the index " "file. This will take some time to rebuild, but will " "not cause any problems.

If that is not enough, " "you can try refreshing the IMAP cache. If you do this, " "you will lose all your local changes for this folder " "and all its subfolders.

" ); QLabel *label = new QLabel(txt, page ); label->setWordWrap(true); topLayout->addWidget( label ); showButtonSeparator( true ); QButtonGroup *group = new QButtonGroup( 0 ); mIndexButton = new QRadioButton( page ); mIndexButton->setText( i18n( "Rebuild &index" ) ); group->addButton( mIndexButton ); topLayout->addWidget( mIndexButton ); KHBox *hbox = new KHBox( page ); QLabel *scopeLabel = new QLabel( i18n( "Scope:" ), hbox ); scopeLabel->setEnabled( false ); mIndexScope = new QComboBox( hbox ); mIndexScope->addItem( i18n( "Only Current Folder" ) ); mIndexScope->addItem( i18n( "Current Folder & All Subfolders" ) ); mIndexScope->addItem( i18n( "All Folders of This Account" ) ); mIndexScope->setEnabled( false ); topLayout->addWidget( hbox ); mCacheButton = new QRadioButton( page ); mCacheButton->setText( i18n( "Refresh &cache" ) ); group->addButton( mCacheButton ); topLayout->addWidget( mCacheButton ); connect ( mIndexButton, SIGNAL( toggled( bool ) ), mIndexScope, SLOT( setEnabled( bool ) ) ); connect ( mIndexButton, SIGNAL( toggled( bool ) ), scopeLabel, SLOT( setEnabled( bool ) ) ); connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) ); } int DImapTroubleShootDialog::run() { DImapTroubleShootDialog d; d.exec(); return d.rc; } void DImapTroubleShootDialog::slotDone() { rc = None; if ( mIndexButton->isChecked() ) { rc = mIndexScope->currentIndex(); } else if ( mCacheButton->isChecked() ) { rc = RefreshCache; } done( Ok ); } KMFolderCachedImap::KMFolderCachedImap( KMFolder *folder, const char *aName ) : KMFolderMaildir( folder, aName ), mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ), mSubfolderState( imapNoInformation ), mIncidencesFor( IncForAdmins ), mIsSelected( false ), mCheckFlags( true ), mReadOnly( false ), mAccount( 0 ), uidMapDirty( true ), uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ), mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ), /*mHoldSyncs( false ),*/ mFolderRemoved( false ), mRecurse( true ), mStatusChangedLocally( false ), mAnnotationFolderTypeChanged( false ), mIncidencesForChanged( false ), mPersonalNamespacesCheckDone( true ), mQuotaInfo(), mAlarmsBlocked( false ), mRescueCommandCount( 0 ), mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values) { setUidValidity( "" ); readUidCache(); mProgress = 0; } KMFolderCachedImap::~KMFolderCachedImap() { if ( !mFolderRemoved ) { writeConfig(); writeUidCache(); } if ( kmkernel->undoStack() ) { kmkernel->undoStack()->folderDestroyed( folder() ); } } void KMFolderCachedImap::initializeFrom( KMFolderCachedImap *parent ) { setAccount( parent->account() ); /* * Now that we have an account, tell it that this folder was created: * if this folder was just removed, then we don't really want to remove * it from the server. */ mAccount->removeDeletedFolder( imapPath() ); setUserRights( parent->userRights() ); } void KMFolderCachedImap::readConfig() { KConfig *config = KMKernel::config(); KConfigGroup group( config, "Folder-" + folder()->idString() ); if ( mImapPath.isEmpty() ) { mImapPath = group.readEntry( "ImapPath" ); } if ( QString( objectName() ).toUpper() == "INBOX" && mImapPath == "/INBOX/" ) { folder()->setLabel( i18n( "inbox" ) ); // for the icon folder()->setSystemFolder( true ); } mNoContent = group.readEntry( "NoContent", false ); mReadOnly = group.readEntry( "ReadOnly", false ); if ( !group.readEntry( "FolderAttributes" ).isEmpty() ) mFolderAttributes = group.readEntry( "FolderAttributes" ); if ( mAnnotationFolderType != "FROMSERVER" ) { mAnnotationFolderType = group.readEntry( "Annotation-FolderType" ); // if there is an annotation, it has to be XML if ( !mAnnotationFolderType.isEmpty() && !mAnnotationFolderType.startsWith( "mail" ) ) { kmkernel->iCalIface().setStorageFormat( folder(), StorageXML ); } } mIncidencesFor = incidencesForFromString( group.readEntry( "IncidencesFor" ) ); mAlarmsBlocked = group.readEntry( "AlarmsBlocked", false ); // kDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath ) // << " readConfig: mIncidencesFor=" << mIncidencesFor; mUserRights = group.readEntry( "UserRights", 0 ); // default is we don't know mOldUserRights = mUserRights; int storageQuotaUsage = group.readEntry( "StorageQuotaUsage", -1 ); int storageQuotaLimit = group.readEntry( "StorageQuotaLimit", -1 ); QString storageQuotaRoot = group.readEntry( "StorageQuotaRoot", QString() ); if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set mQuotaInfo.setName( "STORAGE" ); mQuotaInfo.setRoot( storageQuotaRoot ); if ( storageQuotaUsage > -1 ) { mQuotaInfo.setCurrent( storageQuotaUsage ); } if ( storageQuotaLimit > -1 ) { mQuotaInfo.setMax( storageQuotaLimit ); } } KMFolderMaildir::readConfig(); mStatusChangedLocally = group.readEntry( "StatusChangedLocally", false ); mAnnotationFolderTypeChanged = group.readEntry( "AnnotationFolderTypeChanged", false ); mIncidencesForChanged = group.readEntry( "IncidencesForChanged", false ); if ( mImapPath.isEmpty() ) { mImapPathCreation = group.readEntry("ImapPathCreation"); } } void KMFolderCachedImap::writeConfig() { KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() ); configGroup.writeEntry( "ImapPath", mImapPath ); configGroup.writeEntry( "NoContent", mNoContent ); configGroup.writeEntry( "ReadOnly", mReadOnly ); configGroup.writeEntry( "FolderAttributes", mFolderAttributes ); configGroup.writeEntry( "StatusChangedLocally", mStatusChangedLocally ); if ( !mImapPathCreation.isEmpty() ) { if ( mImapPath.isEmpty() ) { configGroup.writeEntry( "ImapPathCreation", mImapPathCreation ); } else { configGroup.deleteEntry( "ImapPathCreation" ); } } writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); KMFolderMaildir::writeConfig(); } void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig() { KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() ); if ( !folder()->noContent() ) { configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged ); configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType ); configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged ); configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) ); configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked ); configGroup.writeEntry( "UserRights", mUserRights ); configGroup.deleteEntry( "StorageQuotaUsage"); configGroup.deleteEntry( "StorageQuotaRoot"); configGroup.deleteEntry( "StorageQuotaLimit"); if ( mQuotaInfo.isValid() ) { if ( mQuotaInfo.current().isValid() ) { configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() ); } if ( mQuotaInfo.max().isValid() ) { configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() ); } configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() ); } } } int KMFolderCachedImap::create() { int rc = KMFolderMaildir::create(); // FIXME why the below? - till readConfig(); mUnreadMsgs = -1; return rc; } void KMFolderCachedImap::remove() { mFolderRemoved = true; QString part1 = folder()->path() + "/." + dotEscape( objectName() ); QString uidCacheFile = part1 + ".uidcache"; // This is the account folder of an account that was just removed // When this happens, be sure to delete all traces of the cache if ( QFile::exists( uidCacheFile ) ) { unlink( QFile::encodeName( uidCacheFile ) ); } FolderStorage::remove(); } QString KMFolderCachedImap::uidCacheLocation() const { QString sLocation( folder()->path() ); if ( !sLocation.isEmpty() ) { sLocation += '/'; } return sLocation + '.' + dotEscape(fileName()) + ".uidcache"; } int KMFolderCachedImap::readUidCache() { QFile uidcache( uidCacheLocation() ); if ( uidcache.open( QIODevice::ReadOnly ) ) { char buf[1024]; int len = uidcache.readLine( buf, sizeof( buf ) ); if ( len > 0 ) { int cacheVersion; sscanf( buf, "# KMail-UidCache V%d\n", &cacheVersion ); if ( cacheVersion == UIDCACHE_VERSION ) { len = uidcache.readLine( buf, sizeof( buf ) ); if ( len > 0 ) { setUidValidity( QString::fromLocal8Bit( buf ).trimmed() ); len = uidcache.readLine( buf, sizeof( buf ) ); if ( len > 0 ) { // load the last known highest uid from the on disk cache setLastUid( QString::fromLocal8Bit( buf ).trimmed().toULong() ); return 0; } } } } } return -1; } int KMFolderCachedImap::writeUidCache() { if ( uidValidity().isEmpty() || uidValidity() == "INVALID" ) { // No info from the server yet, remove the file. if ( QFile::exists( uidCacheLocation() ) ) { unlink( QFile::encodeName( uidCacheLocation() ) ); } return 0; } QFile uidcache( uidCacheLocation() ); if ( uidcache.open( QIODevice::WriteOnly ) ) { QTextStream str( &uidcache ); str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl; str << uidValidity() << endl; str << lastUid() << endl; uidcache.flush(); fsync( uidcache.handle() ); /* this is probably overkill */ uidcache.close(); return 0; } else { return errno; /* does QFile set errno? */ } } void KMFolderCachedImap::reloadUidMap() { kDebug(5006) <<"Reloading Uid Map"; uidMap.clear(); open( "reloadUid" ); for ( int i = 0; i < count(); ++i ) { KMMsgBase *msg = getMsgBase( i ); if ( !msg ) { continue; } ulong uid = msg->UID(); uidMap.insert( uid, i ); } close( "reloadUid" ); uidMapDirty = false; } KMMessage *KMFolderCachedImap::take( int idx ) { uidMapDirty = true; return KMFolderMaildir::take( idx ); } int KMFolderCachedImap::addMsgInternal( KMMessage *msg, bool newMail, int *index_return ) { // Possible optimization: Only dirty if not filtered below ulong uid = msg->UID(); if ( uid != 0 ) { uidMapDirty = true; } KMFolderOpener openThis( folder(), "KMFolderCachedImap::addMsgInternal" ); int rc = openThis.openResult(); if ( rc ) { kDebug() << "open:" << rc << "of folder:" << label(); return rc; } // Add the message rc = KMFolderMaildir::addMsg( msg, index_return ); if ( newMail && ( imapPath() == "/INBOX/" || ( !GlobalSettings::self()->filterOnlyDIMAPInbox() && (userRights() <= 0 || userRights() & ACLJobs::Administer ) && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) ) { // This is a new message. Filter it mAccount->processNewMsg( msg ); } return rc; } int KMFolderCachedImap::addMsg( KMMessage *msg, int *index_return ) { if ( !canAddMsgNow( msg, index_return ) ) { return 0; } // Add it to storage int rc = KMFolderMaildir::addMsgInternal( msg, index_return, true /*stripUID*/); return rc; } void KMFolderCachedImap::removeMsg( int idx, bool imapQuiet ) { uidMapDirty = true; // Remove it from disk KMFolderMaildir::removeMsg( idx, imapQuiet ); } bool KMFolderCachedImap::canRemoveFolder() const { // If this has subfolders it can't be removed if ( folder() && folder()->child() && folder()->child()->count() > 0 ) { return false; } return true; } int KMFolderCachedImap::rename( const QString &aName, KMFolderDir *aParent ) { Q_UNUSED( aParent ); QString oldName = mAccount->renamedFolder( imapPath() ); if ( oldName.isEmpty() ) { oldName = objectName(); } if ( aName == oldName ) { // Stupid user trying to rename it to it's old name :) return 0; } if ( account() == 0 || imapPath().isEmpty() ) { // We don't think any of this can happen anymore QString err = i18n("You must synchronize with the server before renaming IMAP folders."); KMessageBox::error( 0, err ); return -1; } /* * Make the change appear to the user with setLabel, but we'll do the change * on the server during the next sync. The name() is the name at the time of * the last sync. Only rename if the new one is different. If it's the same, * don't rename, but also make sure the rename is reset, in the case of * A -> B -> A renames. */ if ( objectName() != aName ) { mAccount->addRenamedFolder( imapPath(), folder()->label(), aName ); } else { mAccount->removeRenamedFolder( imapPath() ); } folder()->setLabel( aName ); emit nameChanged(); // for kmailicalifaceimpl return 0; } KMFolder *KMFolderCachedImap::trashFolder() const { QString trashStr = account()->trash(); return kmkernel->dimapFolderMgr()->findIdString( trashStr ); } void KMFolderCachedImap::setLastUid( ulong uid ) { mLastUid = uid; if ( uidWriteTimer == -1 ) { // Write in one minute uidWriteTimer = startTimer( 60000 ); } } void KMFolderCachedImap::timerEvent( QTimerEvent *e ) { Q_UNUSED( e ); killTimer( uidWriteTimer ); uidWriteTimer = -1; writeUidCache(); } ulong KMFolderCachedImap::lastUid() { return mLastUid; } KMMsgBase *KMFolderCachedImap::findByUID( ulong uid ) { bool mapReloaded = false; if ( uidMapDirty ) { reloadUidMap(); mapReloaded = true; } QMap::Iterator it = uidMap.find( uid ); if ( it != uidMap.end() ) { KMMsgBase *msg = getMsgBase( *it ); if ( msg && msg->UID() == uid ) { return msg; } } else { kDebug(5006) <<"Didn't find uid:" << uid <<"in cache!"; } // Not found by now // if ( mapReloaded ) // Not here then return 0; // There could be a problem in the maps. Rebuild them and try again reloadUidMap(); it = uidMap.find( uid ); if ( it != uidMap.end() ) { // Since the uid map is just rebuilt, no need for the sanity check return getMsgBase( *it ); } else { kDebug(5006) <<"Reloaded, but stil didn't find uid:" << uid; } // Then it's not here return 0; } KMAcctCachedImap *KMFolderCachedImap::account() const { if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) { // Find the account mAccount = static_cast( kmkernel->acctMgr()->findByName( objectName() ) ); } return mAccount; } void KMFolderCachedImap::slotTroubleshoot() { const int rc = DImapTroubleShootDialog::run(); if ( rc == DImapTroubleShootDialog::RefreshCache ) { // Refresh cache if ( !account() ) { KMessageBox::sorry( 0, i18n("No account setup for this folder.\n" "Please try running a sync before this.") ); return; } QString str = i18n("Are you sure you want to refresh the IMAP cache of " "the folder %1 and all its subfolders?\nThis will " "remove all changes you have done locally to your " "folders.", label() ); QString s1 = i18n("Refresh IMAP Cache"); QString s2 = i18n("&Refresh"); if ( KMessageBox::warningContinueCancel( 0, str, s1, KGuiItem( s2 ) ) == KMessageBox::Continue ) { account()->invalidateIMAPFolders( this ); } } else { // Rebuild index file switch ( rc ) { case DImapTroubleShootDialog::ReindexAll: { KMFolderCachedImap *rootStorage = dynamic_cast( account()->rootFolder() ); if ( rootStorage ) { rootStorage->createIndexFromContentsRecursive(); } break; } case DImapTroubleShootDialog::ReindexCurrent: createIndexFromContents(); break; case DImapTroubleShootDialog::ReindexRecursive: createIndexFromContentsRecursive(); break; default: return; } KMessageBox::information( 0, i18n( "The index of this folder has been " "recreated." ) ); writeIndex(); kmkernel->getKMMainWidget()->folderSelected(); } } void KMFolderCachedImap::serverSync( bool recurse ) { if ( mSyncState != SYNC_STATE_INITIAL ) { 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?", imapPath(), int( mSyncState ) ), QString(), KGuiItem( i18n("Reset && Sync") ), KStandardGuiItem::cancel() ) == KMessageBox::Yes ) { mSyncState = SYNC_STATE_INITIAL; } else { return; } } mRecurse = recurse; assert( account() ); ProgressItem *progressItem = mAccount->mailCheckProgressItem(); if ( progressItem ) { progressItem->reset(); progressItem->setTotalItems( 100 ); } mProgress = 0; mTentativeHighestUid = 0; // reset, last sync could have been canceled serverSyncInternal(); } QString KMFolderCachedImap::state2String( int state ) const { switch( state ) { case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL"; case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS"; case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES"; case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS"; case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS"; case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS"; case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES"; case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2"; case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS"; case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES"; case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES"; case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES"; case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES"; case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX"; case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS"; case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS"; case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS"; case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS"; case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS"; case SYNC_STATE_GET_QUOTA: return "SYNC_STATE_GET_QUOTA"; case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS"; case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS"; case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER"; case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY"; default: return "Unknown state"; } } /* Progress calculation: each step is assigned a span. Initially the total is 100. But if we skip a step, don't increase the progress. This leaves more room for the step a with variable size (get_messages) connecting 5 getuserrights 5 rename 5 check_uidvalidity 5 create_subfolders 5 put_messages 10 (but it can take a very long time, with many messages....) upload_flags 5 list_subfolders 5 list_subfolders2 0 (all local) delete_subfolders 5 list_messages 10 delete_messages 10 expunge_messages 5 get_messages variable (remaining-5) i.e. minimum 15. check_annotations 0 (rare) set_annotations 0 (rare) get_annotations 2 set_acls 0 (rare) get_acls 3 noContent folders have only a few of the above steps (permissions, and all subfolder stuff), so its steps should be given more span */ // While the server synchronization is running, mSyncState will hold // the state that should be executed next void KMFolderCachedImap::serverSyncInternal() { // This is used to stop processing when we're about to exit // and the current job wasn't cancellable. // For user-requested abort, we'll use signalAbortRequested instead. if ( kmkernel->mailCheckAborted() ) { resetSyncState(); emit folderComplete( this, false ); return; } switch( mSyncState ) { case SYNC_STATE_INITIAL: { mProgress = 0; foldersForDeletionOnServer.clear(); newState( mProgress, i18n("Synchronizing")); open( "cachedimap" ); if ( !noContent() ) { mAccount->addLastUnreadMsgCount( this, countUnread() ); } // Connect to the server (i.e. prepare the slave) ImapAccountBase::ConnectionState cs = mAccount->makeConnection(); if ( cs == ImapAccountBase::Error ) { // Cancelled by user, or slave can't start // We stop here. We're already in SYNC_STATE_INITIAL for the next time. newState( mProgress, i18n( "Error connecting to server %1", mAccount->host() ) ); close( "cachedimap" ); emit folderComplete( this, false ); break; } else if ( cs == ImapAccountBase::Connecting ) { mAccount->setAnnotationCheckPassed( false ); newState( mProgress, i18n("Connecting to %1", mAccount->host() ) ); // We'll wait for the connectionResult signal from the account. connect( mAccount, SIGNAL( connectionResult(int, const QString&) ), this, SLOT( slotConnectionResult(int, const QString&) ) ); break; } else { // Connected mSyncState = SYNC_STATE_GET_USERRIGHTS; // Fall through to next state } } case SYNC_STATE_GET_USERRIGHTS: mSyncState = SYNC_STATE_RENAME_FOLDER; if ( !noContent() && mAccount->hasACLSupport() ) { // Check the user's rights. We do this every time in case they changed. mOldUserRights = mUserRights; newState( mProgress, i18n("Checking permissions")); connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ), this, SLOT( slotReceivedUserRights( KMFolder* ) ) ); mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case break; } case SYNC_STATE_RENAME_FOLDER: { mSyncState = SYNC_STATE_CHECK_UIDVALIDITY; // Returns the new name if the folder was renamed, empty otherwise. bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() ); QString newName = mAccount->renamedFolder( imapPath() ); if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) { newState( mProgress, i18n("Renaming folder") ); CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this ); connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); job->start(); break; } } case SYNC_STATE_CHECK_UIDVALIDITY: mSyncState = SYNC_STATE_CREATE_SUBFOLDERS; if ( !noContent() ) { checkUidValidity(); break; } // Else carry on case SYNC_STATE_CREATE_SUBFOLDERS: mSyncState = SYNC_STATE_PUT_MESSAGES; createNewFolders(); break; case SYNC_STATE_PUT_MESSAGES: mSyncState = SYNC_STATE_UPLOAD_FLAGS; if ( !noContent() ) { uploadNewMessages(); break; } // Else carry on case SYNC_STATE_UPLOAD_FLAGS: mSyncState = SYNC_STATE_LIST_NAMESPACES; if ( !noContent() ) { // We haven't downloaded messages yet, so we need to build the map. if ( uidMapDirty ) { reloadUidMap(); } // Upload flags, unless we know from the ACL that we're not allowed // to do that or they did not change locally if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::WriteFlags ) ) { if ( mStatusChangedLocally ) { uploadFlags(); break; } } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) { if ( mStatusChangedLocally ) { uploadSeenFlags(); break; } } } // Else carry on case SYNC_STATE_LIST_NAMESPACES: if ( this == mAccount->rootFolder() ) { listNamespaces(); break; } mSyncState = SYNC_STATE_LIST_SUBFOLDERS; // Else carry on case SYNC_STATE_LIST_SUBFOLDERS: newState( mProgress, i18n("Retrieving folderlist")); mSyncState = SYNC_STATE_LIST_SUBFOLDERS2; if ( !listDirectory() ) { mSyncState = SYNC_STATE_INITIAL; KMessageBox::error(0, i18n("Error while retrieving the folderlist")); } break; case SYNC_STATE_LIST_SUBFOLDERS2: mSyncState = SYNC_STATE_DELETE_SUBFOLDERS; mProgress += 10; newState( mProgress, i18n("Retrieving subfolders")); listDirectory2(); break; case SYNC_STATE_DELETE_SUBFOLDERS: mSyncState = SYNC_STATE_LIST_MESSAGES; if ( !foldersForDeletionOnServer.isEmpty() ) { newState( mProgress, i18n("Deleting folders from server")); CachedImapJob *job = new CachedImapJob( foldersForDeletionOnServer, CachedImapJob::tDeleteFolders, this ); connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); connect( job, SIGNAL( finished() ), this, SLOT( slotFolderDeletionOnServerFinished() ) ); job->start(); break; } // Not needed, the next step emits newState very quick //newState( mProgress, i18n("No folders to delete from server")); // Carry on case SYNC_STATE_LIST_MESSAGES: mSyncState = SYNC_STATE_DELETE_MESSAGES; if ( !noContent() ) { newState( mProgress, i18n("Retrieving message list")); listMessages(); break; } // Else carry on case SYNC_STATE_DELETE_MESSAGES: mSyncState = SYNC_STATE_EXPUNGE_MESSAGES; if ( !noContent() ) { if ( deleteMessages() ) { // Fine, we will continue with the next state } else { // No messages to delete, skip to GET_MESSAGES newState( mProgress, i18n("No messages to delete...")); mSyncState = SYNC_STATE_GET_MESSAGES; serverSyncInternal(); } break; } // Else carry on case SYNC_STATE_EXPUNGE_MESSAGES: mSyncState = SYNC_STATE_GET_MESSAGES; if ( !noContent() ) { newState( mProgress, i18n("Expunging deleted messages")); CachedImapJob *job = new CachedImapJob( QString(), CachedImapJob::tExpungeFolder, this ); connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); job->start(); break; } // Else carry on case SYNC_STATE_GET_MESSAGES: mSyncState = SYNC_STATE_HANDLE_INBOX; if ( !noContent() ) { if ( !mMsgsForDownload.isEmpty() ) { newState( mProgress, i18n("Retrieving new messages")); CachedImapJob *job = new CachedImapJob( mMsgsForDownload, CachedImapJob::tGetMessage, this ); connect( job, SIGNAL( progress( unsigned long, unsigned long ) ), this, SLOT( slotProgress( unsigned long, unsigned long ) ) ); connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) ); connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); job->start(); mMsgsForDownload.clear(); break; } else { newState( mProgress, i18n("No new messages from server")); /* * There were no messages to download, but it could be that we * uploaded some which we didn't need to download again because we * already knew the uid. Now that we are sure there is nothing to * download, and everything that had to be deleted on the server * has been deleted, adjust our local notion of the highest uid * seen thus far. */ slotUpdateLastUid(); if ( mLastUid == 0 && uidWriteTimer == -1 ) { // This is probably a new and empty folder. Write the UID cache writeUidCache(); } } } // Else carry on case SYNC_STATE_HANDLE_INBOX: // Wrap up the 'download emails' stage. We always end up at 95 here. mProgress = 95; mSyncState = SYNC_STATE_TEST_ANNOTATIONS; #define KOLAB_FOLDERTEST "/vendor/kolab/folder-test" case SYNC_STATE_TEST_ANNOTATIONS: mSyncState = SYNC_STATE_GET_ANNOTATIONS; // The first folder with user rights to write annotations if ( !mAccount->annotationCheckPassed() && ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) && !imapPath().isEmpty() && imapPath() != "/" ) { kDebug(5006) <<"Setting test attribute on folder:" << folder()->prettyUrl(); newState( mProgress, i18n("Checking annotation support")); KUrl url = mAccount->getUrl(); url.setPath( imapPath() ); KMail::AnnotationList annotations; // to be set KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" ); annotations.append( attr ); kDebug(5006) <<"Setting test attribute to"<< url; KIO::Job *job = AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations ); ImapAccountBase::jobData jd( url.url(), folder() ); jd.cancellable = true; // we can always do so later mAccount->insertJob( job, jd ); connect( job, SIGNAL( result( KJob * ) ), SLOT( slotTestAnnotationResult( KJob * ) ) ); break; } case SYNC_STATE_GET_ANNOTATIONS: { #define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type" #define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for" //#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/* mSyncState = SYNC_STATE_SET_ANNOTATIONS; bool needToGetInitialAnnotations = false; if ( !noContent() ) { // for a folder we didn't create ourselves: get annotation from server if ( mAnnotationFolderType == "FROMSERVER" ) { needToGetInitialAnnotations = true; mAnnotationFolderType.clear(); } else { updateAnnotationFolderType(); } } /* * First retrieve the annotation, so that we know we have to set it * if it's not set. On the other hand, if the user changed the * contentstype, there's no need to get first. */ if ( !noContent() && mAccount->hasAnnotationSupport() && ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) { QStringList annotations; // list of annotations to be fetched if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() ) annotations << KOLAB_FOLDERTYPE; if ( !mIncidencesForChanged ) annotations << KOLAB_INCIDENCESFOR; if ( !annotations.isEmpty() ) { newState( mProgress, i18n("Retrieving annotations")); KUrl url = mAccount->getUrl(); url.setPath( imapPath() ); AnnotationJobs::MultiGetAnnotationJob *job = AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations ); ImapAccountBase::jobData jd( url.url(), folder() ); jd.cancellable = true; mAccount->insertJob(job, jd); connect( job, SIGNAL(annotationResult(const QString&, const QString&, bool)), SLOT(slotAnnotationResult(const QString&, const QString&, bool)) ); connect( job, SIGNAL(result(KJob *)), SLOT(slotGetAnnotationResult(KJob *)) ); break; } } } // case case SYNC_STATE_SET_ANNOTATIONS: mSyncState = SYNC_STATE_SET_ACLS; if ( !noContent() && mAccount->hasAnnotationSupport() && ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) { newState( mProgress, i18n("Setting annotations")); KUrl url = mAccount->getUrl(); url.setPath( imapPath() ); KMail::AnnotationList annotations; // to be set if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) { KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared", mAnnotationFolderType ); annotations.append( attr ); kDebug(5006) <<"Setting folder-type annotation for" << label() << "to" << mAnnotationFolderType; } if ( mIncidencesForChanged ) { const QString val = incidencesForToString( mIncidencesFor ); KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared", val ); annotations.append( attr ); kDebug(5006) <<"Setting incidences-for annotation for" << label() << "to" << val; } if ( !annotations.isEmpty() ) { KIO::Job *job = AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations ); ImapAccountBase::jobData jd( url.url(), folder() ); jd.cancellable = true; // we can always do so later mAccount->insertJob(job, jd); connect(job, SIGNAL(annotationChanged( const QString&, const QString&, const QString& ) ), SLOT( slotAnnotationChanged( const QString&, const QString&, const QString& ) )); connect(job, SIGNAL(result(KJob *)), SLOT(slotSetAnnotationResult(KJob *))); break; } } case SYNC_STATE_SET_ACLS: mSyncState = SYNC_STATE_GET_ACLS; if ( !noContent() && mAccount->hasACLSupport() && ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) { bool hasChangedACLs = false; ACLList::ConstIterator it = mACLList.begin(); for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) { hasChangedACLs = (*it).changed; } if ( hasChangedACLs ) { newState( mProgress, i18n("Setting permissions")); KUrl url = mAccount->getUrl(); url.setPath( imapPath() ); KIO::Job *job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url, mACLList ); ImapAccountBase::jobData jd( url.url(), folder() ); mAccount->insertJob(job, jd); connect(job, SIGNAL(result(KJob *)), SLOT(slotMultiSetACLResult(KJob *))); connect(job, SIGNAL(aclChanged( const QString&, int )), SLOT(slotACLChanged( const QString&, int )) ); break; } } case SYNC_STATE_GET_ACLS: mSyncState = SYNC_STATE_GET_QUOTA; if ( !noContent() && mAccount->hasACLSupport() ) { newState( mProgress, i18n( "Retrieving permissions" ) ); mAccount->getACL( folder(), mImapPath ); connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); break; } case SYNC_STATE_GET_QUOTA: // Continue with the subfolders mSyncState = SYNC_STATE_FIND_SUBFOLDERS; if ( !noContent() && mAccount->hasQuotaSupport() ) { newState( mProgress, i18n("Getting quota information")); KUrl url = mAccount->getUrl(); url.setPath( imapPath() ); KIO::Job *job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url ); ImapAccountBase::jobData jd( url.url(), folder() ); mAccount->insertJob(job, jd); connect( job, SIGNAL( storageQuotaResult( const QuotaInfo& ) ), SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) ); connect( job, SIGNAL(result(KJob *)), SLOT(slotQuotaResult(KJob *)) ); break; } case SYNC_STATE_FIND_SUBFOLDERS: { mProgress = 98; newState( mProgress, i18n("Updating cache file")); mSyncState = SYNC_STATE_SYNC_SUBFOLDERS; mSubfoldersForSync.clear(); mCurrentSubfolder = 0; if ( folder() && folder()->child() ) { QList::const_iterator it; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { KMFolderNode *node = *it; if ( !node->isDir() ) { KMFolderCachedImap *storage = static_cast( static_cast( node )->storage() ); // Only sync folders that have been accepted by the server if ( !storage->imapPath().isEmpty() && // and that were not just deleted from it !foldersForDeletionOnServer.contains( storage->imapPath() ) ) { mSubfoldersForSync << storage; } else { kDebug(5006) <<"Do not add" << storage->label() << "to synclist"; } } } } // All done for this folder. mProgress = 100; // all done newState( mProgress, i18n("Synchronization done")); KUrl url = mAccount->getUrl(); url.setPath( imapPath() ); kmkernel->iCalIface().folderSynced( folder(), url ); } if ( !mRecurse ) // "check mail for this folder" only mSubfoldersForSync.clear(); // Carry on case SYNC_STATE_SYNC_SUBFOLDERS: { if ( mCurrentSubfolder ) { disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); mCurrentSubfolder = 0; } if ( mSubfoldersForSync.isEmpty() ) { mSyncState = SYNC_STATE_INITIAL; mAccount->addUnreadMsgCount( this, countUnread() ); // before closing close( "cachedimap" ); emit folderComplete( this, true ); } else { mCurrentSubfolder = mSubfoldersForSync.front(); mSubfoldersForSync.pop_front(); connect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); assert( !mCurrentSubfolder->imapPath().isEmpty() ); mCurrentSubfolder->setAccount( account() ); bool recurse = mCurrentSubfolder->noChildren() ? false : true; mCurrentSubfolder->serverSync( recurse ); } } break; default: kDebug(5006) << "WARNING: no such state" << int(mSyncState); } } void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString &errorMsg ) { disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ), this, SLOT( slotConnectionResult(int, const QString&) ) ); if ( !errorCode ) { // Success mSyncState = SYNC_STATE_GET_USERRIGHTS; mProgress += 5; serverSyncInternal(); } else { // Error (error message already shown by the account) newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ) ); emit folderComplete( this, false ); } } /* find new messages (messages without a UID) */ QList KMFolderCachedImap::findNewMessages() { QList result; for ( int i = 0; i < count(); ++i ) { KMMsgBase *msg = getMsgBase( i ); if ( !msg ) { // what goes on if getMsg() returns 0? continue; } if ( msg->UID() == 0 ) { result.append( msg->getMsgSerNum() ); } } return result; } /* Upload new messages to server */ void KMFolderCachedImap::uploadNewMessages() { QList newMsgs = findNewMessages(); if ( !newMsgs.isEmpty() ) { if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) { newState( mProgress, i18n("Uploading messages to server")); CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this ); connect( job, SIGNAL( progress( unsigned long, unsigned long ) ), this, SLOT( slotPutProgress( unsigned long, unsigned long ) ) ); connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); job->start(); return; } else { KMCommand *command = rescueUnsyncedMessages(); connect( command, SIGNAL( completed( KMCommand * ) ), this, SLOT( serverSyncInternal() ) ); } } else { // nothing to upload if ( mUserRights != mOldUserRights && ( mOldUserRights & KMail::ACLJobs::Insert ) && !( mUserRights & KMail::ACLJobs::Insert ) ) { // write access revoked KMessageBox::information( 0, i18n("

Your access rights to folder %1 have been restricted, " "it will no longer be possible to add messages to this folder.

", folder()->prettyUrl() ), i18n("Access rights revoked"), "KMailACLRevocationNotification" ); } } newState( mProgress, i18n("No messages to upload to server")); serverSyncInternal(); } /* Progress info during uploadNewMessages */ void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total ) { // (going from mProgress to mProgress+10) int progressSpan = 10; newState( mProgress + ( progressSpan * done ) / total, QString() ); if ( done == total ) { // we're done mProgress += progressSpan; } } /* Upload message flags to server */ void KMFolderCachedImap::uploadFlags() { if ( !uidMap.isEmpty() ) { mStatusFlagsJobs = 0; newState( mProgress, i18n("Uploading status of messages to server")); // FIXME DUPLICATED FROM KMFOLDERIMAP QMap< QString, QStringList > groups; //open(); //already done for ( int i = 0; i < count(); ++i ) { KMMsgBase *msg = getMsgBase( i ); if ( !msg || msg->UID() == 0 ) { // Either not a valid message or not one that is on the server yet continue; } QString flags = KMFolderImap::statusToFlags( msg->status(), mPermanentFlags ); // Collect uids for each typem of flags. QString uid; uid.setNum( msg->UID() ); groups[flags].append( uid ); } QMap< QString, QStringList >::Iterator dit; for ( dit = groups.begin(); dit != groups.end(); ++dit ) { QByteArray flags = dit.key().toLatin1(); QStringList sets = KMFolderImap::makeSets( (*dit), true ); mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap.... // Send off a status setting job for each set. for ( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) { QString imappath = imapPath() + ";UID=" + ( *slit ); mAccount->setImapStatus( folder(), imappath, flags ); } } // FIXME END DUPLICATED FROM KMFOLDERIMAP if ( mStatusFlagsJobs ) { connect( mAccount, SIGNAL( imapStatusChanged( KMFolder*, const QString&, bool ) ), this, SLOT( slotImapStatusChanged( KMFolder*, const QString&, bool ) ) ); return; } } newState( mProgress, i18n("No messages to upload to server") ); serverSyncInternal(); } void KMFolderCachedImap::uploadSeenFlags() { if ( !uidMap.isEmpty() ) { mStatusFlagsJobs = 0; newState( mProgress, i18n("Uploading status of messages to server")); QList seenUids, unseenUids; for( int i = 0; i < count(); ++i ) { KMMsgBase* msg = getMsgBase( i ); if( !msg || msg->UID() == 0 ) // Either not a valid message or not one that is on the server yet continue; if ( msg->status().isOld() || msg->status().isRead() ) seenUids.append( msg->UID() ); else unseenUids.append( msg->UID() ); } if ( !seenUids.isEmpty() ) { QStringList sets = KMFolderImap::makeSets( seenUids, true ); mStatusFlagsJobs += sets.count(); for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) { QString imappath = imapPath() + ";UID=" + ( *it ); mAccount->setImapSeenStatus( folder(), imappath, true ); } } if ( !unseenUids.isEmpty() ) { QStringList sets = KMFolderImap::makeSets( unseenUids, true ); mStatusFlagsJobs += sets.count(); for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) { QString imappath = imapPath() + ";UID=" + ( *it ); mAccount->setImapSeenStatus( folder(), imappath, false ); } } if ( mStatusFlagsJobs ) { connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ), this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) ); return; } } newState( mProgress, i18n("No messages to upload to server")); serverSyncInternal(); } void KMFolderCachedImap::slotImapStatusChanged( KMFolder *folder, const QString&, bool cont ) { if ( mSyncState == SYNC_STATE_INITIAL ) { kDebug(5006) <<"IMAP status changed but reset"; return; // we were reset } if ( folder->storage() == this ) { --mStatusFlagsJobs; if ( mStatusFlagsJobs == 0 || !cont ) { // done or aborting disconnect( mAccount, SIGNAL( imapStatusChanged( KMFolder*, const QString&, bool ) ), this, SLOT( slotImapStatusChanged( KMFolder*, const QString&, bool ) ) ); } if ( mStatusFlagsJobs == 0 && cont ) { mProgress += 5; serverSyncInternal(); } } } // This is not perfect, what if the status didn't really change? Oh well ... void KMFolderCachedImap::setStatus( int idx, const MessageStatus &status, bool toggle ) { KMFolderMaildir::setStatus( idx, status, toggle ); mStatusChangedLocally = true; } void KMFolderCachedImap::setStatus( QList &ids, const MessageStatus &status, bool toggle ) { KMFolderMaildir::setStatus( ids, status, toggle ); mStatusChangedLocally = true; } /* Upload new folders to server */ void KMFolderCachedImap::createNewFolders() { QList newFolders = findNewFolders(); if ( !newFolders.isEmpty() ) { newState( mProgress, i18n("Creating subfolders on server")); CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this ); connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) ); connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) ); job->start(); } else { serverSyncInternal(); } } QList KMFolderCachedImap::findNewFolders() { QList newFolders; if ( folder() && folder()->child() ) { QList::const_iterator it; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { KMFolderNode *node = *it; if ( !node->isDir() ) { if ( static_cast(node)->folderType() != KMFolderTypeCachedImap ) { kError(5006) <<"KMFolderCachedImap::findNewFolders(): ARGH!!!" << node->name() << " is not an IMAP folder\n"; assert( 0 ); } KMFolderCachedImap *folder = static_cast( static_cast( node )->storage() ); if ( folder->imapPath().isEmpty() ) { newFolders << folder; } } } } return newFolders; } bool KMFolderCachedImap::deleteMessages() { /* Delete messages from cache that are gone from the server */ QList msgsForDeletion; /* * It is not possible to just go over all indices and remove * them one by one because the index list can get resized under * us. So use msg pointers instead. */ QMap::const_iterator it = uidMap.constBegin(); for ( ; it != uidMap.constEnd(); it++ ) { ulong uid ( it.key() ); if ( uid != 0 && !uidsOnServer.contains( uid ) ) { msgsForDeletion.append( getMsg( *it ) ); } } if ( !msgsForDeletion.isEmpty() ) { removeMessages( msgsForDeletion ); } if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) ) { return false; } /* Delete messages from the server that we don't have anymore */ if ( !uidsForDeletionOnServer.isEmpty() ) { newState( mProgress, i18n("Deleting removed messages from server")); QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true ); uidsForDeletionOnServer.clear(); kDebug(5006) <<"Deleting" << sets.count() << "sets of messages from server folder" << imapPath(); CachedImapJob *job = new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this ); connect( job, SIGNAL( result( KMail::FolderJob * ) ), this, SLOT( slotDeleteMessagesResult( KMail::FolderJob * ) ) ); job->start(); return true; } else { return false; } } void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob *job ) { if ( job->error() ) { // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages mSyncState = SYNC_STATE_GET_MESSAGES; } mProgress += 10; serverSyncInternal(); } void KMFolderCachedImap::checkUidValidity() { /* * IMAP root folders don't seem to have a UID validity setting. * Also, don't try the uid validity on new folders. */ if ( imapPath().isEmpty() || imapPath() == "/" ) { // Just proceed serverSyncInternal(); } else { newState( mProgress, i18n("Checking folder validity")); CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this ); connect( job, SIGNAL(permanentFlags(int)), SLOT(slotPermanentFlags(int)) ); connect( job, SIGNAL( result( KMail::FolderJob* ) ), this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) ); job->start(); } } void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob *job ) { if ( job->error() ) { // there was an error and the user chose "continue" /* * We can't continue doing anything in the same folder though, * it would delete all mails. But we can continue to subfolders * if any. Well we can also try annotation/acl stuff... */ mSyncState = SYNC_STATE_HANDLE_INBOX; } mProgress += 5; serverSyncInternal(); } void KMFolderCachedImap::slotPermanentFlags(int flags) { mPermanentFlags = flags; } void KMFolderCachedImap::listMessages() { bool groupwareOnly = GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() && folder()->isSystemFolder() && mImapPath == "/INBOX/"; /* * Don't list messages on the root folder, and skip the inbox, if this is * the inbox of a groupware-only dimap account. */ if ( imapPath() == "/" || groupwareOnly ) { serverSyncInternal(); return; } if ( !mAccount->slave() ) { // sync aborted resetSyncState(); emit folderComplete( this, false ); return; } uidsOnServer.clear(); uidsOnServer.reserve( count() * 2 ); uidsForDeletionOnServer.clear(); mMsgsForDownload.clear(); mUidsForDownload.clear(); CachedImapJob *job = new CachedImapJob( FolderJob::tListMessages, this ); connect( job, SIGNAL( result( KMail::FolderJob * ) ), this, SLOT( slotGetLastMessagesResult( KMail::FolderJob * ) ) ); job->start(); } void KMFolderCachedImap::slotGetLastMessagesResult( KMail::FolderJob *job ) { getMessagesResult( job, true ); } // Connected to the listMessages job in CachedImapJob void KMFolderCachedImap::slotGetMessagesData( KIO::Job *job, const QByteArray &data ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( job ); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen kDebug(5006) <<"could not find job!?!?!"; /* * Be sure to reset the sync state, if the listing was partial we * would otherwise delete not-listed mail locally, and on the next * sync on the server as well. */ mSyncState = SYNC_STATE_HANDLE_INBOX; serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */ return; } (*it).cdata += data; int pos = (*it).cdata.indexOf("\r\n--IMAPDIGEST"); if ( pos > 0 ) { int a = (*it).cdata.indexOf("\r\nX-uidValidity:"); if (a != -1) { int b = (*it).cdata.indexOf("\r\n", a + 17); setUidValidity((*it).cdata.mid(a + 17, b - a - 17)); } a = (*it).cdata.indexOf("\r\nX-Access:"); /* * Only trust X-Access (i.e. the imap select info) if we don't know * mUserRights. The latter is more accurate (checked on every sync) * whereas X-Access is only updated when selecting the folder again, * which might not happen if using RMB / Check Mail in this folder. * We don't need two (potentially conflicting) sources for the * readonly setting, in any case. */ if ( a != -1 && mUserRights == -1 ) { int b = (*it).cdata.indexOf( "\r\n", a + 12 ); const QString access = (*it).cdata.mid( a + 12, b - a - 12 ); setReadOnly( access == "Read only" ); } (*it).cdata.remove( 0, pos ); } pos = (*it).cdata.indexOf( "\r\n--IMAPDIGEST", 1 ); // Start with something largish when rebuilding the cache if ( uidsOnServer.size() == 0 ) { uidsOnServer.reserve( KMail::nextPrime( 2000 ) ); } while ( pos >= 0 ) { /* KMMessage msg; msg.fromString((*it).cdata.mid(16, pos - 16)); const int flags = msg.headerField("X-Flags").toInt(); const ulong size = msg.headerField("X-Length").toULong(); const ulong uid = msg.UID(); */ // The below is optimized for speed, not prettiness. The commented out chunk // above was the solution copied from kmfolderimap, and it's 15-20% slower. const QByteArray& entry( (*it).cdata ); const int indexOfUID = entry.indexOf("X-UID", 16); const int startOfUIDValue = indexOfUID + 7; const int indexOfLength = entry.indexOf("X-Length", startOfUIDValue ); // we know length comes after UID const int startOfLengthValue = indexOfLength + 10; const int indexOfFlags = entry.indexOf("X-Flags", startOfLengthValue ); // we know flags comes last const int startOfFlagsValue = indexOfFlags + 9; const int flags = entry.mid( startOfFlagsValue, entry.indexOf( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt(); const ulong size = entry.mid( startOfLengthValue, entry.indexOf( '\r', startOfLengthValue ) - startOfLengthValue ).toULong(); const ulong uid = entry.mid( startOfUIDValue, entry.indexOf( '\r', startOfUIDValue ) - startOfUIDValue ).toULong(); const bool deleted = ( flags & 8 ); if ( !deleted ) { if ( uid != 0 ) { if ( uidsOnServer.capacity() <= uidsOnServer.size() ) { uidsOnServer.reserve( KMail::nextPrime( uidsOnServer.size() * 2 ) ); kDebug( 5006 ) << "Resizing to:" << uidsOnServer.capacity(); } uidsOnServer.insert( uid ); } bool redownload = false; if ( uid <= lastUid() ) { /* * If this message UID is not present locally, then it must * have been deleted by the user, so we delete it on the * server also. If we don't have delete permissions on the server, * re-download the message, it must have vanished by some error, or * while we still thought we were allowed to delete (ACL change). * * This relies heavily on lastUid() being correct at all times. */ KMMsgBase *existingMessage = findByUID( uid ); if ( !existingMessage ) { if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::Delete ) ) { uidsForDeletionOnServer << uid; } else { redownload = true; } } else { /* * If this is a read only folder, ignore status updates from the * server since we can't write our status back our local version * is what has to be considered correct. */ if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) { /* The message is OK, update flags */ KMFolderImap::flagsToStatus( existingMessage, flags, false, mReadOnly ? INT_MAX : mPermanentFlags ); } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) { KMFolderImap::seenFlagToStatus( existingMessage, flags ); } } } if ( uid > lastUid() || redownload ) { /* * The message is new since the last sync, but we might have * just uploaded it, in which case the uid map already contains it. */ if ( !uidMap.contains( uid ) ) { mMsgsForDownload << KMail::CachedImapJob::MsgForDownload( uid, flags, size ); if ( imapPath() == "/INBOX/" ) { mUidsForDownload << uid; } } // Remember the highest uid and once the download is completed, update mLastUid if ( uid > mTentativeHighestUid ) { mTentativeHighestUid = uid; } } } (*it).cdata.remove( 0, pos ); (*it).done++; pos = (*it).cdata.indexOf( "\r\n--IMAPDIGEST", 1 ); } } void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet ) { mProgress += 10; if ( job->error() ) { // error listing messages but the user chose to continue mContentState = imapNoInformation; mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder } else { if ( lastSet ) { // always true here (this comes from online-imap...) mContentState = imapFinished; mStatusChangedLocally = false; // we are up to date again } } serverSyncInternal(); } void KMFolderCachedImap::slotProgress( unsigned long done, unsigned long total ) { int progressSpan = 100 - 5 - mProgress; int additionalProgress = ( total == 0 ) ? progressSpan : ( progressSpan * done ) / total; // Progress info while retrieving new emails // (going from mProgress to mProgress+progressSpan) newState( mProgress + additionalProgress, QString() ); } void KMFolderCachedImap::setAccount( KMAcctCachedImap *aAccount ) { assert( ::qobject_cast( aAccount ) ); mAccount = aAccount; if ( imapPath() == "/" ) { aAccount->setFolder( folder() ); } // Folder was renamed in a previous session, and the user didn't sync yet QString newName = mAccount->renamedFolder( imapPath() ); if ( !newName.isEmpty() ) { folder()->setLabel( newName ); } if ( !folder() || !folder()->child() || !folder()->child()->count() ) { return; } QList::const_iterator it; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { KMFolderNode *node = *it; if ( !node->isDir() ) { static_cast( static_cast( node )->storage() )->setAccount( aAccount ); } } } void KMFolderCachedImap::listNamespaces() { ImapAccountBase::ListType type = ImapAccountBase::List; if ( mAccount->onlySubscribedFolders() ) { type = ImapAccountBase::ListSubscribed; } kDebug(5006) <<"listNamespaces" << mNamespacesToList; if ( mNamespacesToList.isEmpty() ) { mSyncState = SYNC_STATE_DELETE_SUBFOLDERS; mPersonalNamespacesCheckDone = true; QStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS]; ns += mAccount->namespaces()[ImapAccountBase::SharedNS]; mNamespacesToCheck = ns.count(); for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it ) { if ( (*it).isEmpty() ) { // ignore empty listings as they have been listed before --mNamespacesToCheck; continue; } KMail::ListJob *job = new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) ); job->setHonorLocalSubscription( true ); connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ) ), this, SLOT( slotCheckNamespace( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ) ) ); job->start(); } if ( mNamespacesToCheck == 0 ) { serverSyncInternal(); } return; } mPersonalNamespacesCheckDone = false; QString ns = mNamespacesToList.front(); mNamespacesToList.pop_front(); mSyncState = SYNC_STATE_LIST_SUBFOLDERS2; newState( mProgress, i18n("Retrieving folders for namespace %1", ns) ); KMail::ListJob *job = new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( ns ) ); job->setNamespace( ns ); job->setHonorLocalSubscription( true ); connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ) ), this, SLOT( slotListResult( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ) ) ); job->start(); } void KMFolderCachedImap::slotCheckNamespace( const QStringList &subfolderNames, const QStringList &subfolderPaths, const QStringList &subfolderMimeTypes, const QStringList &subfolderAttributes, const ImapAccountBase::jobData &jobData ) { Q_UNUSED( subfolderPaths ); Q_UNUSED( subfolderMimeTypes ); Q_UNUSED( subfolderAttributes ); --mNamespacesToCheck; kDebug(5006) <<"slotCheckNamespace" << subfolderNames <<",remain=" << mNamespacesToCheck; // get a correct foldername: // strip / and make sure it does not contain the delimiter QString name = jobData.path.mid( 1, jobData.path.length() - 2 ); name.remove( mAccount->delimiterForNamespace( name ) ); if ( name.isEmpty() ) { // should not happen kWarning(5006) <<"slotCheckNamespace: ignoring empty folder!"; return; } folder()->createChildFolder(); QList::const_iterator it; KMFolderNode *node = 0; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { if ( !(*it)->isDir() && (*it)->name() == name ) { node = *it; break; } } if ( !subfolderNames.isEmpty() ) { if ( node ) { // folder exists so we have nothing to do - it will be listed later kDebug(5006) <<"found namespace folder" << name; } else { // create folder kDebug(5006) <<"create namespace folder" << name; KMFolder *newFolder = folder()->child()->createFolder( name, false, KMFolderTypeCachedImap ); if ( newFolder ) { KMFolderCachedImap *f = static_cast( newFolder->storage() ); f->setImapPath( mAccount->addPathToNamespace( name ) ); f->setNoContent( true ); f->setAccount( mAccount ); f->close( "cachedimap" ); kmkernel->dimapFolderMgr()->contentsChanged(); } } } else { if ( node ) { kDebug(5006) <<"delete namespace folder" << name; KMFolder *fld = static_cast( node ); kmkernel->dimapFolderMgr()->remove( fld ); } } if ( mNamespacesToCheck == 0 ) { // all namespaces are done so continue with the next step serverSyncInternal(); } } bool KMFolderCachedImap::listDirectory() { if ( !mAccount->slave() ) { // sync aborted resetSyncState(); emit folderComplete( this, false ); return false; } mSubfolderState = imapInProgress; // get the folders ImapAccountBase::ListType type = ImapAccountBase::List; if ( mAccount->onlySubscribedFolders() ) { type = ImapAccountBase::ListSubscribed; } KMail::ListJob *job = new KMail::ListJob( mAccount, type, this ); job->setHonorLocalSubscription( true ); connect( job, SIGNAL( receivedFolders( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ) ), this, SLOT( slotListResult( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ) ) ); job->start(); return true; } void KMFolderCachedImap::slotListResult( const QStringList &folderNames, const QStringList &folderPaths, const QStringList &folderMimeTypes, const QStringList &folderAttributes, const ImapAccountBase::jobData &jobData ) { Q_UNUSED( jobData ); mSubfolderNames = folderNames; mSubfolderPaths = folderPaths; mSubfolderMimeTypes = folderMimeTypes; mSubfolderAttributes = folderAttributes; mSubfolderState = imapFinished; folder()->createChildFolder(); bool root = ( this == mAccount->rootFolder() ); QList toRemove; bool emptyList = ( root && mSubfolderNames.empty() ); if ( !emptyList ) { QList::const_iterator it; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { KMFolderNode *node = *it; if (!node->isDir() ) { KMFolderCachedImap *f = static_cast( static_cast( node )->storage() ); if ( !mSubfolderNames.contains(node->name()) ) { QString name = node->name(); // as more than one namespace can be listed in the root folder we need to make sure // that the folder is within the current namespace bool isInNamespace = ( jobData.curNamespace.isEmpty() || jobData.curNamespace == mAccount->namespaceForFolder( f ) ); // ignore some cases bool ignore = root && ( f->imapPath() == "/INBOX/" || mAccount->isNamespaceFolder( name ) || !isInNamespace ); // This subfolder isn't present on the server if ( !f->imapPath().isEmpty() && !ignore ) { // The folder has an imap path set, so it has been // on the server before. Delete it locally. toRemove.append( f->folder() ); kDebug(5006) << node->name() << "isn't on the server. It has an imapPath -> delete it locally"; } } else { // folder both local and on server //kDebug(5006) << node->name() <<" is on the server."; /** * Store the folder attributes for every subfolder. */ int index = mSubfolderNames.indexOf( node->name() ); f->mFolderAttributes = folderAttributes[ index ]; } } else { //kDebug(5006) <<"skipping dir node:" << node->name(); } } } QList::const_iterator jt; for ( jt = toRemove.constBegin(); jt != toRemove.constEnd(); ++jt ) { if ( *jt ) { rescueUnsyncedMessagesAndDeleteFolder( *jt ); } } mProgress += 5; // just in case there is nothing to rescue slotRescueDone( 0 ); } void KMFolderCachedImap::listDirectory2() { QString path = folder()->path(); kmkernel->dimapFolderMgr()->quiet( true ); bool root = ( this == mAccount->rootFolder() ); if ( root && !mAccount->hasInbox() ) { KMFolderCachedImap *f = 0; KMFolderNode *node = 0; // create the INBOX QList::const_iterator it = folder()->child()->begin(); for ( ; it != folder()->child()->end(); ++it ) { if ( !(*it)->isDir() && (*it)->name() == "INBOX" ) { node = *it; break; } } if ( node ) { f = static_cast( static_cast( node )->storage() ); } else { KMFolder *newFolder = folder()->child()->createFolder( "INBOX", true, KMFolderTypeCachedImap ); if ( newFolder ) { f = static_cast( newFolder->storage() ); } } if ( f ) { f->setAccount( mAccount ); f->setImapPath( "/INBOX/" ); f->folder()->setLabel( i18n("inbox") ); } if ( !node ) { if ( f ) { f->close( "cachedimap" ); } kmkernel->dimapFolderMgr()->contentsChanged(); } // so we have an INBOX mAccount->setHasInbox( true ); } if ( root && !mSubfolderNames.isEmpty() ) { KMFolderCachedImap *parent = findParent( mSubfolderPaths.first(), mSubfolderNames.first() ); if ( parent ) { kDebug(5006) << "Pass listing to" << parent->label(); mSubfolderNames.clear(); } } // Find all subfolders present on server but not on disk QVector foldersNewOnServer; for (int i = 0; i < mSubfolderNames.count(); i++) { // Find the subdir, if already present KMFolderCachedImap *f = 0; KMFolderNode *node = 0; for ( QList::ConstIterator it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) if ( !(*it)->isDir() && (*it)->name() == mSubfolderNames[i] ) { node = *it; break; } if ( !node ) { // This folder is not present here // Either it's new on the server, or we just deleted it. QString subfolderPath = mSubfolderPaths[i]; // The code used to look at the uidcache to know if it was "just deleted". // But this breaks with noContent folders and with shared folders. // So instead we keep a list in the account. bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath ); // That list is saved/restored across sessions, but to avoid any mistake, // ask for confirmation if the folder was deleted in a previous session // (could be that the folder was deleted & recreated meanwhile from another client...) if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) { locallyDeleted = KMessageBox::warningYesNo( 0, i18n("

It seems that the folder %1 was deleted. " "Do you want to delete it from the server?

", mSubfolderNames[i] ), QString(), KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::Yes; } if ( locallyDeleted ) { kDebug(5006) << subfolderPath << "was deleted locally => delete on server."; // grab all subsubfolders too foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath ); } else { kDebug(5006) << subfolderPath << "is a new folder on the server => create local cache"; foldersNewOnServer.append( i ); } } else { // Folder found locally if ( static_cast(node)->folderType() == KMFolderTypeCachedImap ) { f = dynamic_cast(static_cast(node)->storage()); } if ( f ) { // kDebug(5006) <<"folder("<name()<<")->imapPath()=" << f->imapPath() // << "\nSetting imapPath" << mSubfolderPaths[i]; // Write folder settings f->setAccount( mAccount ); f->setNoContent( mSubfolderMimeTypes[i] == "inode/directory" ); f->setNoChildren( mSubfolderMimeTypes[i] == "message/digest" ); f->setImapPath( mSubfolderPaths[i] ); } } } /* In case we are ignoring non-groupware folders, and this is the groupware * main account, find out the contents types of folders that have newly * appeared on the server. Otherwise just create them and finish listing. * If a folder is already known to be locally unsubscribed, it won't be * listed at all, on this level, so these are only folders that we are * seeing for the first time. */ /* Note: We ask the globalsettings, and not the current state of the * kmkernel->iCalIface().isEnabled(), since that is false during the * very first sync, where we already want to filter. */ if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id() && mAccount->hasAnnotationSupport() && GlobalSettings::self()->theIMAPResourceEnabled() && !foldersNewOnServer.isEmpty() ) { QStringList paths; for ( int i = 0; i < foldersNewOnServer.count(); ++i ) { paths << mSubfolderPaths[ foldersNewOnServer[i] ]; } AnnotationJobs::MultiUrlGetAnnotationJob *job = AnnotationJobs::multiUrlGetAnnotation( mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE ); const QString dummystring; ImapAccountBase::jobData jd( dummystring, folder() ); jd.cancellable = true; mAccount->insertJob( job, jd ); connect( job, SIGNAL( result( KJob * ) ), SLOT( slotMultiUrlGetAnnotationResult( KJob * ) ) ); } else { createFoldersNewOnServerAndFinishListing( foldersNewOnServer ); } } void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const QVector foldersNewOnServer ) { for ( int i = 0; i < foldersNewOnServer.count(); ++i ) { int idx = foldersNewOnServer[i]; KMFolder *newFolder = folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap ); if ( newFolder ) { KMFolderCachedImap *f = dynamic_cast(newFolder->storage()); kDebug(5006) <<" ####### Locally creating folder" << mSubfolderNames[idx]; f->close( "cachedimap" ); f->setAccount( mAccount ); f->mAnnotationFolderType = "FROMSERVER"; f->setNoContent( mSubfolderMimeTypes[idx] == "inode/directory" ); f->setNoChildren( mSubfolderMimeTypes[idx] == "message/digest" ); f->setImapPath( mSubfolderPaths[idx] ); f->mFolderAttributes = mSubfolderAttributes[idx]; kmkernel->dimapFolderMgr()->contentsChanged(); } else { kDebug(5006) <<"can't create folder" << mSubfolderNames[idx]; } } kmkernel->dimapFolderMgr()->quiet( false ); emit listComplete( this ); if ( !mPersonalNamespacesCheckDone ) { // we're not done with the namespaces mSyncState = SYNC_STATE_LIST_NAMESPACES; } serverSyncInternal(); } //----------------------------------------------------------------------------- KMFolderCachedImap *KMFolderCachedImap::findParent( const QString &path, const QString &name ) { QString parent = path.left( path.length() - name.length() - 2 ); if ( parent.length() > 1 ) { // extract name of the parent parent = parent.right( parent.length() - 1 ); if ( parent != label() ) { // look for a better parent QList::const_iterator it; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { KMFolderNode *node = *it; if ( node->name() == parent ) { KMFolder *fld = static_cast( node ); KMFolderCachedImap *imapFld = static_cast( fld->storage() ); return imapFld; } } } } return 0; } void KMFolderCachedImap::slotSubFolderComplete( KMFolderCachedImap *sub, bool success ) { Q_UNUSED( sub ); if ( success ) { serverSyncInternal(); } else { // success == false means the sync was aborted. if ( mCurrentSubfolder ) { Q_ASSERT( sub == mCurrentSubfolder ); disconnect( mCurrentSubfolder, SIGNAL( folderComplete( KMFolderCachedImap*, bool ) ), this, SLOT( slotSubFolderComplete( KMFolderCachedImap*, bool ) ) ); mCurrentSubfolder = 0; } mSubfoldersForSync.clear(); mSyncState = SYNC_STATE_INITIAL; close( "cachedimap" ); emit folderComplete( this, false ); } } void KMFolderCachedImap::slotSimpleData( KIO::Job *job, const QByteArray &data ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( job ); if ( it == mAccount->jobsEnd() ) { return; } QBuffer buff( &(*it).data ); buff.open( QIODevice::WriteOnly | QIODevice::Append ); buff.write( data.data(), data.size() ); buff.close(); } FolderJob *KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder, const QString &partSpecifier, const AttachmentStrategy* ) const { Q_UNUSED( partSpecifier ); QList msgList; msgList.append( msg ); CachedImapJob *job = new CachedImapJob( msgList, jt, folder ? static_cast( folder->storage() ) : 0 ); job->setParentFolder( this ); return job; } FolderJob *KMFolderCachedImap::doCreateJob( QList &msgList, const QString &sets, FolderJob::JobType jt, KMFolder *folder ) const { //FIXME: how to handle sets here? Q_UNUSED( sets ); CachedImapJob *job = new CachedImapJob( msgList, jt, folder ? static_cast( folder->storage() ) : 0 ); job->setParentFolder( this ); return job; } void KMFolderCachedImap::setUserRights( unsigned int userRights ) { mUserRights = userRights; writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); } void KMFolderCachedImap::slotReceivedUserRights( KMFolder *folder ) { if ( folder->storage() == this ) { disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ), this, SLOT( slotReceivedUserRights( KMFolder* ) ) ); if ( mUserRights == 0 ) { // didn't work mUserRights = -1; // error code (used in folderdia) } else { setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 ); } mProgress += 5; serverSyncInternal(); } } void KMFolderCachedImap::setReadOnly( bool readOnly ) { if ( readOnly != mReadOnly ) { mReadOnly = readOnly; emit readOnlyChanged( folder() ); } } void KMFolderCachedImap::slotReceivedACL( KMFolder *folder, KIO::Job*, const KMail::ACLList &aclList ) { if ( folder->storage() == this ) { disconnect( mAccount, SIGNAL( receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ) ), this, SLOT( slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ) ) ); mACLList = aclList; serverSyncInternal(); } } void KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo &info ) { setQuotaInfo( info ); } void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info ) { if ( info != mQuotaInfo ) { mQuotaInfo = info; writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); emit folderSizeChanged(); } } void KMFolderCachedImap::setACLList( const ACLList& arr ) { mACLList = arr; } void KMFolderCachedImap::slotMultiSetACLResult( KJob *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( static_cast( job ) ); if ( it == mAccount->jobsEnd() ) { return; // Shouldn't happen } if ( (*it).parent != folder() ) { return; // Shouldn't happen } if ( job->error() ) { // Display error but don't abort the sync just for this // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel static_cast(job)->ui()->setWindow( 0 ); static_cast(job)->ui()->showErrorMessage(); } else { kmkernel->iCalIface().addFolderChange( folder(), ACLChanged ); } if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } serverSyncInternal(); } void KMFolderCachedImap::slotACLChanged( const QString &userId, int permissions ) { // The job indicates success in changing the permissions for this user // -> we note that it's been done. for ( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) { if ( (*it).userId == userId && (*it).permissions == permissions ) { if ( permissions == -1 ) { // deleted mACLList.erase( it ); } else { // added/modified (*it).changed = false; } return; } } } // called by KMAcctCachedImap::killAllJobs void KMFolderCachedImap::resetSyncState() { if ( mSyncState == SYNC_STATE_INITIAL ) { return; } mSubfoldersForSync.clear(); mSyncState = SYNC_STATE_INITIAL; close( "cachedimap" ); // Don't use newState here, it would revert to mProgress // (which is less than the current value when listing messages) KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem(); QString str = i18n("Aborted"); if ( progressItem ) { progressItem->setStatus( str ); } emit statusMsg( str ); } void KMFolderCachedImap::slotIncreaseProgress() { mProgress += 5; } void KMFolderCachedImap::newState( int progress, const QString &syncStatus ) { KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem(); if ( progressItem ) { progressItem->setCompletedItems( progress ); } if ( !syncStatus.isEmpty() ) { QString str; // For a subfolder, show the label. But for the main folder, it's already shown. if ( mAccount->imapFolder() == this ) { str = syncStatus; } else { str = QString( "%1: %2" ).arg( label() ).arg( syncStatus ); } if ( progressItem ) { progressItem->setStatus( str ); } emit statusMsg( str ); } if ( progressItem ) { progressItem->updateProgress(); } } void KMFolderCachedImap::setSubfolderState( imapState state ) { mSubfolderState = state; if ( state == imapNoInformation && folder()->child() ) { // pass through to children QList::const_iterator it; for ( it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { KMFolderNode *node = *it; if ( node->isDir() ) { continue; } KMFolder *folder = static_cast( node ); static_cast( folder->storage())->setSubfolderState( state ); } } } void KMFolderCachedImap::setImapPath( const QString &path ) { mImapPath = path; } // mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc) // It is updated from the folder contents type and whether it's a standard resource folder. // This happens during the syncing phase and during initFolder for a new folder. // Don't do it earlier, e.g. from setContentsType: // on startup, it's too early there to know if this is a standard resource folder. void KMFolderCachedImap::updateAnnotationFolderType() { QString oldType = mAnnotationFolderType; QString oldSubType; int dot = oldType.indexOf( '.' ); if ( dot != -1 ) { oldType.truncate( dot ); oldSubType = mAnnotationFolderType.mid( dot + 1 ); } QString newType, newSubType; // We want to store an annotation on the folder only if using the kolab storage. if ( kmkernel->iCalIface().storageFormat( folder() ) == StorageXML ) { newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType ); if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) ) { newSubType = "default"; } else { // preserve unknown subtypes, like drafts etc. And preserve ".default" too. newSubType = oldSubType; } } if ( newType != oldType || newSubType != oldSubType ) { mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString() : '.'+newSubType ); mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync kDebug(5006) << mImapPath <<": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE"; } // Ensure that further readConfig()s don't lose mAnnotationFolderType writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); } void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor ) { if ( mIncidencesFor != incfor ) { mIncidencesFor = incfor; mIncidencesForChanged = true; } } void KMFolderCachedImap::slotAnnotationResult( const QString &entry, const QString &value, bool found ) { if ( entry == KOLAB_FOLDERTYPE ) { /* * There are four cases. * 1) no content-type on server -> set it * 2) different content-type on server, locally changed -> set it * (we don't even come here) * 3) different (known) content-type on server, no local change -> get it * 4) different unknown content-type on server, probably some older * version -> set it */ if ( found ) { QString type = value; QString subtype; int dot = value.indexOf( '.' ); if ( dot != -1 ) { type.truncate( dot ); subtype = value.mid( dot + 1 ); } bool foundKnownType = false; for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) { FolderContentsType contentsType = static_cast( i ); if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) { // Case 3: known content-type on server, get it if ( contentsType != ContentsTypeMail ) { kmkernel->iCalIface().setStorageFormat( folder(), StorageXML ); } mAnnotationFolderType = value; if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent() && GlobalSettings::self()->theIMAPResourceEnabled() && subtype == "default" ) { // Truncate subtype if this folder can't be a default resource // folder for us, although it apparently is for someone else. mAnnotationFolderType = type; kDebug(5006) << mImapPath << ": slotGetAnnotationResult: parent folder is" << folder()->parent()->owner()->idString() << "=> truncating annotation to" << value; } setContentsType( contentsType ); mAnnotationFolderTypeChanged = false; // we changed it, not the user foundKnownType = true; /* * Users don't read events/contacts/etc. in kmail, so mark them all * as read. This is done in cachedimapjob when getting new messages, * but do it here too, for the initial set of messages when we * didn't know this was a resource folder yet, for old folders, etc. */ if ( contentsType != ContentsTypeMail ) { markUnreadAsRead(); } // Ensure that further readConfig()s don't lose mAnnotationFolderType writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); break; } } if ( !foundKnownType && !mReadOnly ) { // Case 4: server has strange content-type, set it to what we need mAnnotationFolderTypeChanged = true; } // TODO handle subtype (inbox, drafts, sentitems, junkemail) } else if ( !mReadOnly ) { // Case 1: server doesn't have content-type, set it mAnnotationFolderTypeChanged = true; } } else if ( entry == KOLAB_INCIDENCESFOR ) { if ( found ) { mIncidencesFor = incidencesForFromString( value ); Q_ASSERT( mIncidencesForChanged == false ); } } } void KMFolderCachedImap::slotGetAnnotationResult( KJob *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( static_cast( job ) ); Q_ASSERT( it != mAccount->jobsEnd() ); if ( it == mAccount->jobsEnd() ) { return; // Shouldn't happen } Q_ASSERT( (*it).parent == folder() ); if ( (*it).parent != folder() ) { return; // Shouldn't happen } AnnotationJobs::GetAnnotationJob *annjob = static_cast( job ); if ( annjob->error() ) { if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) { // that's when the imap server doesn't support annotations if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) { KMessageBox::error( 0, i18n( "The IMAP server %1 does not have support for IMAP annotations. " "The XML storage cannot be used on this server; " "please re-configure KMail differently.", mAccount->host() ) ); } mAccount->setHasNoAnnotationSupport(); } else { kWarning(5006) <<"slotGetAnnotationResult:" << job->errorString(); } } if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } mProgress += 2; serverSyncInternal(); } void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KJob *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( job ); Q_ASSERT( it != mAccount->jobsEnd() ); if ( it == mAccount->jobsEnd() ) { return; // Shouldn't happen } Q_ASSERT( (*it).parent == folder() ); if ( (*it).parent != folder() ) { return; // Shouldn't happen } QVector folders; AnnotationJobs::MultiUrlGetAnnotationJob *annjob = static_cast( job ); if ( annjob->error() ) { if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) { // that's when the imap server doesn't support annotations if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() ) { KMessageBox::error( 0, i18n( "The IMAP server %1 does not support annotations. " "The XML storage cannot be used on this server, " "please re-configure KMail differently", mAccount->host() ) ); } mAccount->setHasNoAnnotationSupport(); } else { kWarning(5006) <<"slotGetMultiUrlAnnotationResult:" << job->errorString(); } } else { // we got the annotation allright, let's filter out the ones with the wrong type QMap annotations = annjob->annotations(); QMap::Iterator it = annotations.begin(); for ( ; it != annotations.end(); ++it ) { const QString folderPath = it.key(); const QString annotation = it.value(); kDebug(5006) <<"Folder:" << folderPath <<" has type:" << annotation; // we're only interested in the main type QString type( annotation ); int dot = annotation.indexOf( '.' ); if ( dot != -1 ) { type.truncate( dot ); } type = type.simplified(); const int idx = mSubfolderPaths.indexOf( folderPath ); const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory"; if ( ( isNoContent && type.isEmpty() ) || ( !type.isEmpty() && type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) { folders.append( idx ); kDebug(5006) <<" subscribing to:" << folderPath; } else { kDebug(5006) <<" automatically unsubscribing from:" << folderPath; mAccount->changeLocalSubscription( folderPath, false ); } } } if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } createFoldersNewOnServerAndFinishListing( folders ); } void KMFolderCachedImap::slotQuotaResult( KJob *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( static_cast( job ) ); Q_ASSERT( it != mAccount->jobsEnd() ); if ( it == mAccount->jobsEnd() ) { return; // Shouldn't happen } Q_ASSERT( (*it).parent == folder() ); if ( (*it).parent != folder() ) { return; // Shouldn't happen } QuotaJobs::GetStorageQuotaJob *quotajob = static_cast( job ); QuotaInfo empty; if ( quotajob->error() ) { if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) { // that's when the imap server doesn't support quota mAccount->setHasNoQuotaSupport(); setQuotaInfo( empty ); } else { kWarning(5006) <<"slotGetQuotaResult:" << job->errorString(); } } if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } mProgress += 2; serverSyncInternal(); } void KMFolderCachedImap::slotAnnotationChanged( const QString &entry, const QString &attribute, const QString &value ) { kDebug(5006) << entry << attribute << value; if ( entry == KOLAB_FOLDERTYPE ) { mAnnotationFolderTypeChanged = false; } else if ( entry == KOLAB_INCIDENCESFOR ) { mIncidencesForChanged = false; /* * The incidences-for changed, we must trigger the freebusy creation. * HACK: in theory we would need a new enum value for this. */ kmkernel->iCalIface().addFolderChange( folder(), ACLChanged ); } } void KMFolderCachedImap::slotTestAnnotationResult( KJob *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( static_cast( job ) ); Q_ASSERT( it != mAccount->jobsEnd() ); if ( it == mAccount->jobsEnd() ) { return; // Shouldn't happen } Q_ASSERT( (*it).parent == folder() ); if ( (*it).parent != folder() ) { return; // Shouldn't happen } mAccount->setAnnotationCheckPassed( true ); if ( job->error() ) { kDebug(5006) <<"Test Annotation was not passed, disabling annotation support"; mAccount->setHasNoAnnotationSupport( ); } else { kDebug(5006) <<"Test Annotation was passed OK"; } if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } serverSyncInternal(); } void KMFolderCachedImap::slotSetAnnotationResult( KJob *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob( static_cast( job ) ); if ( it == mAccount->jobsEnd() ) { return; // Shouldn't happen } if ( (*it).parent != folder() ) { return; // Shouldn't happen } bool cont = true; if ( job->error() ) { /* * Don't show error if the server doesn't support ANNOTATEMORE * and this folder only contains mail. */ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION && contentsType() == ContentsTypeMail ) { if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } else { cont = mAccount->handleJobError( static_cast( job ), i18n( "Error while setting annotation: " ) + '\n' ); } } } else { if ( mAccount->slave() ) { mAccount->removeJob( static_cast( job ) ); } } if ( cont ) { serverSyncInternal(); } } void KMFolderCachedImap::slotUpdateLastUid() { if ( mTentativeHighestUid != 0 ) { setLastUid( mTentativeHighestUid ); } mTentativeHighestUid = 0; } bool KMFolderCachedImap::isMoveable() const { return ( hasChildren() == HasNoChildren && !folder()->isSystemFolder() ) ? true : false; } void KMFolderCachedImap::slotFolderDeletionOnServerFinished() { for ( QStringList::const_iterator it = foldersForDeletionOnServer.constBegin(); it != foldersForDeletionOnServer.constEnd(); ++it ) { KUrl url( mAccount->getUrl() ); url.setPath( *it ); kmkernel->iCalIface().folderDeletedOnServer( url ); } serverSyncInternal(); } int KMFolderCachedImap::createIndexFromContentsRecursive() { if ( !folder() || !folder()->child() ) { return 0; } for ( QList::Iterator it = folder()->child()->begin(); it != folder()->child()->end(); ++it ) { if ( !(*it)->isDir() ) { KMFolderCachedImap *storage = static_cast(static_cast(*it)->storage()); kDebug(5006) <<"Re-indexing:" << storage->folder()->label(); int rv = storage->createIndexFromContentsRecursive(); if ( rv > 0 ) { return rv; } } } return createIndexFromContents(); } void KMFolderCachedImap::setAlarmsBlocked( bool blocked ) { mAlarmsBlocked = blocked; } bool KMFolderCachedImap::alarmsBlocked() const { return mAlarmsBlocked; } bool KMFolderCachedImap::isCloseToQuota() const { bool closeToQuota = false; if ( mQuotaInfo.isValid() && mQuotaInfo.max().toInt() > 0 ) { const int ratio = mQuotaInfo.current().toInt() * 100 / mQuotaInfo.max().toInt(); //kDebug(5006) <<"Quota ratio:" << ratio <<"%" << mQuotaInfo.toString(); closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() ); } //kDebug(5006) <<"Folder:" << folder()->prettyURL() <<" is over quota:" << closeToQuota; return closeToQuota; } KMCommand* KMFolderCachedImap::rescueUnsyncedMessages() { QList newMsgs = findNewMessages(); kDebug(5006) << newMsgs <<" of" << count(); if ( newMsgs.isEmpty() ) return 0; KMFolder *dest = 0; bool manualMove = true; while ( GlobalSettings::autoLostFoundMove() ) { // find the inbox of this account KMFolder *inboxFolder = kmkernel->findFolderById( QString(".%1.directory/INBOX").arg( account()->id() ) ); if ( !inboxFolder ) { kWarning(5006) <<"inbox not found!"; break; } KMFolderDir *inboxDir = inboxFolder->child(); if ( !inboxDir && !inboxFolder->storage() ) break; assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap ); // create lost+found folder if needed KMFolderNode *node; KMFolder *lfFolder = 0; if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) { kDebug(5006) <<"creating lost+found folder"; KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder( i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir ); if ( !folder || !folder->storage() ) break; static_cast( folder->storage() )->initializeFrom( static_cast( inboxFolder->storage() ) ); folder->storage()->setContentsType( KMail::ContentsTypeMail ); folder->storage()->writeConfig(); lfFolder = folder; } else { kDebug(5006) <<"found lost+found folder"; lfFolder = dynamic_cast( node ); } if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() ) break; // create subfolder for this incident QDate today = QDate::currentDate(); QString baseName = folder()->label() + '-' + QString::number( today.year() ) + (today.month() < 10 ? "0" : "" ) + QString::number( today.month() ) + (today.day() < 10 ? "0" : "" ) + QString::number( today.day() ); QString name = baseName; int suffix = 0; while ( (node = lfFolder->child()->hasNamedFolder( name )) ) { ++suffix; name = baseName + '-' + QString::number( suffix ); } kDebug(5006) <<"creating lost+found folder" << name; dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() ); if ( !dest || !dest->storage() ) break; static_cast( dest->storage() )->initializeFrom( static_cast( lfFolder->storage() ) ); dest->storage()->setContentsType( contentsType() ); dest->storage()->writeConfig(); KMessageBox::sorry( 0, i18n("

There are new messages in folder %1, which " "have not been uploaded to the server yet, but the folder has been deleted " "on the server or you do not " "have sufficient access rights on the folder to upload them.

" "

All affected messages will therefore be moved to %2 " "to avoid data loss.

", folder()->prettyUrl(), dest->prettyUrl() ), i18n("Insufficient access rights") ); manualMove = false; break; } if ( manualMove ) { const QString msg ( i18n( "

There are new messages in this folder (%1), which " "have not been uploaded to the server yet, but the folder has been deleted " "on the server or you do not " "have sufficient access rights on the folder now to upload them. " "Please contact your administrator to allow upload of new messages " "to you, or move them out of this folder.

" "

Do you want to move these messages to another folder now?

", folder()->prettyUrl() ) ); if ( KMessageBox::warningYesNo( 0, msg, QString(), KGuiItem( i18n("Move") ), KGuiItem( i18n("Do Not Move") ) ) == KMessageBox::Yes ) { KMail::FolderSelectionDialog dlg( kmkernel->getKMMainWidget(), i18n("Move Messages to Folder"), true ); if ( dlg.exec() ) { dest = dlg.folder(); } } } if ( dest ) { QList msgs; for( int i = 0; i < count(); ++i ) { KMMsgBase *msg = getMsgBase( i ); if( !msg ) continue; /* what goes on if getMsg() returns 0? */ if ( msg->UID() == 0 ) msgs.append( msg ); } KMCommand *command = new KMMoveCommand( dest, msgs ); command->start(); return command; } return 0; } void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root ) { kDebug(5006) << folder << root; if ( root ) mToBeDeletedAfterRescue.append( folder ); folder->open( "KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder" ); KMFolderCachedImap* storage = dynamic_cast( folder->storage() ); if ( storage ) { KMCommand *command = storage->rescueUnsyncedMessages(); if ( command ) { connect( command, SIGNAL(completed(KMCommand*)), SLOT(slotRescueDone(KMCommand*)) ); ++mRescueCommandCount; } else { // nothing to rescue, close folder // (we don't need to close it in the other case, it will be deleted anyway) folder->close( "KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder" ); } } if ( folder->child() ) { for ( KMFolderNodeList::ConstIterator it = folder->child()->constBegin(); it != folder->child()->constEnd(); ++it ) { KMFolderNode *node = *it; if ( node && !node->isDir() ) { KMFolder *subFolder = static_cast( node ); rescueUnsyncedMessagesAndDeleteFolder( subFolder, false ); } } } } void KMFolderCachedImap::slotRescueDone(KMCommand * command) { // FIXME: check command result if ( command ) --mRescueCommandCount; if ( mRescueCommandCount > 0 ) return; for ( QList::ConstIterator it = mToBeDeletedAfterRescue.constBegin(); it != mToBeDeletedAfterRescue.constEnd(); ++it ) { kmkernel->dimapFolderMgr()->remove( *it ); } mToBeDeletedAfterRescue.clear(); serverSyncInternal(); } #include "kmfoldercachedimap.moc"