diff --git a/kmail/cachedimapjob.cpp b/kmail/cachedimapjob.cpp index 96a2b1e840..8c63396120 100644 --- a/kmail/cachedimapjob.cpp +++ b/kmail/cachedimapjob.cpp @@ -1,841 +1,841 @@ /* -*- mode: C++; c-file-style: "gnu" -*- * * This file is part of KMail, the KDE mail client. * Copyright (c) 2002-2004 Bo Thorsen * 2002-2003 Steffen Hansen * 2002-2003 Zack Rusin * * KMail is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation. * * KMail 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. */ #ifdef HAVE_CONFIG_H #include #endif #include "cachedimapjob.h" #include "imapaccountbase.h" #include "kmfoldermgr.h" #include "kmfolder.h" #include "kmfoldercachedimap.h" #include "kmailicalifaceimpl.h" #include "kmacctcachedimap.h" #include "kmmsgdict.h" #include "maildirjob.h" #include "scalix.h" #include "util.h" #include #include #include #include namespace KMail { // Get messages CachedImapJob::CachedImapJob( const QValueList& msgs, JobType type, KMFolderCachedImap* folder ) : FolderJob( type ), mFolder( folder ), mMsgsForDownload( msgs ), mTotalBytes(0), mMsg(0), mParentFolder( 0 ) { QValueList::ConstIterator it = msgs.begin(); for ( ; it != msgs.end() ; ++it ) mTotalBytes += (*it).size; } // Put messages CachedImapJob::CachedImapJob( const QPtrList& msgs, JobType type, KMFolderCachedImap* folder ) : FolderJob( msgs, QString::null, type, folder?folder->folder():0 ), mFolder( folder ), mTotalBytes( msgs.count() ), // we abuse it as "total number of messages" mMsg( 0 ), mParentFolder( 0 ) { } CachedImapJob::CachedImapJob( const QValueList& msgs, JobType type, KMFolderCachedImap* folder ) : FolderJob( QPtrList(), QString::null, type, folder?folder->folder():0 ), mFolder( folder ), mSerNumMsgList( msgs ), mTotalBytes( msgs.count() ), mMsg( 0 ), mParentFolder ( 0 ) { } // Add sub folders CachedImapJob::CachedImapJob( const QValueList& fList, JobType type, KMFolderCachedImap* folder ) : FolderJob( type ), mFolder( folder ), mFolderList( fList ), mMsg( 0 ), mParentFolder ( 0 ) { } // Rename folder CachedImapJob::CachedImapJob( const QString& string1, JobType type, KMFolderCachedImap* folder ) : FolderJob( type ), mFolder(folder), mMsg( 0 ), mString( string1 ), mParentFolder ( 0 ) { assert( folder ); assert( type != tDeleteMessage ); // moved to another ctor } // Delete folders or messages CachedImapJob::CachedImapJob( const QStringList& foldersOrMsgs, JobType type, KMFolderCachedImap* folder ) : FolderJob( type ), mFolder( folder ), mFoldersOrMessages( foldersOrMsgs ), mMsg( 0 ), mParentFolder( 0 ) { assert( folder ); } // Other jobs (list messages,expunge folder, check uid validity) CachedImapJob::CachedImapJob( JobType type, KMFolderCachedImap* folder ) : FolderJob( type ), mFolder( folder ), mMsg( 0 ), mParentFolder ( 0 ) { assert( folder ); } CachedImapJob::~CachedImapJob() { mAccount->mJobList.remove(this); } void CachedImapJob::execute() { mSentBytes = 0; if( !mFolder ) { if( !mMsgList.isEmpty() ) { mFolder = static_cast(mMsgList.first()->storage()); } } assert( mFolder ); mAccount = mFolder->account(); assert( mAccount != 0 ); if( mAccount->makeConnection() != ImapAccountBase::Connected ) { // No connection to the IMAP server kdDebug(5006) << "mAccount->makeConnection() failed" << endl; mPassiveDestructor = true; delete this; return; } else mPassiveDestructor = false; // All necessary conditions have been met. Register this job mAccount->mJobList.append(this); /** * The Scalix server requires to send him a custom X-SCALIX-ID command * to switch it into a special mode. * * This should be done once after the login and before the first command. */ if ( mAccount->groupwareType() == KMAcctCachedImap::GroupwareScalix ) { if ( !mAccount->sentCustomLoginCommand() ) { QByteArray packedArgs; QDataStream stream( packedArgs, IO_WriteOnly ); const QString command = QString( "X-SCALIX-ID " ); const QString argument = QString( "(\"name\" \"Evolution\" \"version\" \"2.10.0\")" ); stream << (int) 'X' << 'N' << command << argument; const KURL url = mAccount->getUrl(); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.items << mFolder->label(); // for the err msg KIO::SimpleJob *simpleJob = KIO::special( url.url(), packedArgs, false ); KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob); mAccount->insertJob(simpleJob, jd); mAccount->setSentCustomLoginCommand( true ); } } switch( mType ) { case tGetMessage: slotGetNextMessage(); break; case tPutMessage: slotPutNextMessage(); break; case tDeleteMessage: slotDeleteNextMessages(); break; case tExpungeFolder: expungeFolder(); break; case tAddSubfolders: slotAddNextSubfolder(); break; case tDeleteFolders: slotDeleteNextFolder(); break; case tCheckUidValidity: checkUidValidity(); break; case tRenameFolder: renameFolder(mString); break; case tListMessages: listMessages(); break; default: assert( 0 ); } } void CachedImapJob::listMessages() { KURL url = mAccount->getUrl(); url.setPath( mFolder->imapPath() + ";UID=1:*;SECTION=FLAGS RFC822.SIZE"); KIO::SimpleJob *job = KIO::get(url, false, false); KIO::Scheduler::assignJobToSlave( mAccount->slave(), job ); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.cancellable = true; mAccount->insertJob( job, jd ); connect( job, SIGNAL( result(KIO::Job *) ), this, SLOT( slotListMessagesResult( KIO::Job* ) ) ); // send the data directly for KMFolderCachedImap connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ), mFolder, SLOT( slotGetMessagesData( KIO::Job* , const QByteArray& ) ) ); } void CachedImapJob::slotDeleteNextMessages( KIO::Job* job ) { if (job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if( job->error() ) { mAccount->handleJobError( job, i18n( "Error while deleting messages on the server: " ) + '\n' ); delete this; return; } mAccount->removeJob(it); } if( mFoldersOrMessages.isEmpty() ) { // No more messages to delete delete this; return; } QString uids = mFoldersOrMessages.front(); mFoldersOrMessages.pop_front(); KURL url = mAccount->getUrl(); url.setPath( mFolder->imapPath() + QString::fromLatin1(";UID=%1").arg(uids) ); KIO::SimpleJob *simpleJob = KIO::file_delete( url, false ); KIO::Scheduler::assignJobToSlave( mAccount->slave(), simpleJob ); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); mAccount->insertJob( simpleJob, jd ); connect( simpleJob, SIGNAL( result(KIO::Job *) ), this, SLOT( slotDeleteNextMessages(KIO::Job *) ) ); } void CachedImapJob::expungeFolder() { KURL url = mAccount->getUrl(); // Special URL that means EXPUNGE url.setPath( mFolder->imapPath() + QString::fromLatin1(";UID=*") ); KIO::SimpleJob *job = KIO::file_delete( url, false ); KIO::Scheduler::assignJobToSlave( mAccount->slave(), job ); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); mAccount->insertJob( job, jd ); connect( job, SIGNAL( result(KIO::Job *) ), this, SLOT( slotExpungeResult(KIO::Job *) ) ); } void CachedImapJob::slotExpungeResult( KIO::Job * job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if (job->error()) { mErrorCode = job->error(); mAccount->handleJobError( job, i18n( "Error while deleting messages on the server: " ) + '\n' ); } else mAccount->removeJob(it); delete this; } void CachedImapJob::slotGetNextMessage(KIO::Job * job) { if (job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if (job->error()) { mErrorCode = job->error(); mAccount->handleJobError( job, i18n( "Error while retrieving message on the server: " ) + '\n' ); delete this; return; } ulong size = 0; if ((*it).data.size() > 0) { ulong uid = mMsg->UID(); size = mMsg->msgSizeServer(); // Convert CR/LF to LF. size_t dataSize = (*it).data.size(); dataSize = Util::crlf2lf( (*it).data.data(), dataSize ); // always <= (*it).data.resize( dataSize ); mMsg->setComplete( true ); mMsg->fromByteArray( (*it).data ); mMsg->setUID(uid); mMsg->setMsgSizeServer(size); mMsg->setTransferInProgress( false ); int index = 0; mFolder->addMsgInternal( mMsg, true, &index ); if ( kmkernel->iCalIface().isResourceFolder( mFolder->folder() ) ) { mFolder->setStatus( index, KMMsgStatusRead, false ); } emit messageRetrieved( mMsg ); if ( index > 0 ) mFolder->unGetMsg( index ); } else { emit messageRetrieved( 0 ); } mMsg = 0; mSentBytes += size; emit progress( mSentBytes, mTotalBytes ); mAccount->removeJob(it); } else mFolder->quiet( true ); if( mMsgsForDownload.isEmpty() ) { mFolder->quiet( false ); delete this; return; } MsgForDownload mfd = mMsgsForDownload.front(); mMsgsForDownload.pop_front(); mMsg = new KMMessage; mMsg->setUID(mfd.uid); mMsg->setMsgSizeServer(mfd.size); if( mfd.flags > 0 ) KMFolderImap::flagsToStatus(mMsg, mfd.flags, true, GlobalSettings::allowLocalFlags() ? mFolder->permanentFlags() : INT_MAX); KURL url = mAccount->getUrl(); url.setPath(mFolder->imapPath() + QString(";UID=%1;SECTION=BODY.PEEK[]").arg(mfd.uid)); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.cancellable = true; mMsg->setTransferInProgress(true); KIO::SimpleJob *simpleJob = KIO::get(url, false, false); KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob); mAccount->insertJob(simpleJob, jd); connect(simpleJob, SIGNAL(processedSize(KIO::Job *, KIO::filesize_t)), this, SLOT(slotProcessedSize(KIO::Job *, KIO::filesize_t))); connect(simpleJob, SIGNAL(result(KIO::Job *)), this, SLOT(slotGetNextMessage(KIO::Job *))); connect(simpleJob, SIGNAL(data(KIO::Job *, const QByteArray &)), mFolder, SLOT(slotSimpleData(KIO::Job *, const QByteArray &))); } void CachedImapJob::slotProcessedSize(KIO::Job *, KIO::filesize_t processed) { emit progress( mSentBytes + processed, mTotalBytes ); } void CachedImapJob::slotPutNextMessage() { mMsg = 0; // First try the message list if( !mMsgList.isEmpty() ) { mMsg = mMsgList.first(); mMsgList.removeFirst(); } // Now try the serial number list while( mMsg == 0 && !mSerNumMsgList.isEmpty() ) { unsigned long serNum = mSerNumMsgList.first(); mSerNumMsgList.pop_front(); // Find the message with this serial number int i = 0; KMFolder* aFolder = 0; KMMsgDict::instance()->getLocation( serNum, &aFolder, &i ); if( mFolder->folder() != aFolder ) // This message was moved or something continue; mMsg = mFolder->getMsg( i ); } if( !mMsg ) { // No message found for upload delete this; return; } KURL url = mAccount->getUrl(); QString flags = KMFolderImap::statusToFlags( mMsg->status(), mFolder->permanentFlags() ); url.setPath( mFolder->imapPath() + ";SECTION=" + flags ); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); mMsg->setUID( 0 ); // for the index QCString cstr(mMsg->asString()); int a = cstr.find("\nX-UID: "); int b = cstr.find('\n', a); if (a != -1 && b != -1 && cstr.find("\n\n") > a) cstr.remove(a, b-a); QCString mData(cstr.length() + cstr.contains('\n')); unsigned int i = 0; for( char *ch = cstr.data(); *ch; ch++ ) { if ( *ch == '\n' ) { mData.at(i) = '\r'; i++; } mData.at(i) = *ch; i++; } jd.data = mData; jd.msgList.append( mMsg ); mMsg->setTransferInProgress(true); KIO::SimpleJob *simpleJob = KIO::put(url, 0, false, false, false); KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob); mAccount->insertJob(simpleJob, jd); connect( simpleJob, SIGNAL( result(KIO::Job *) ), SLOT( slotPutMessageResult(KIO::Job *) ) ); connect( simpleJob, SIGNAL( dataReq(KIO::Job *, QByteArray &) ), SLOT( slotPutMessageDataReq(KIO::Job *, QByteArray &) ) ); connect( simpleJob, SIGNAL( data(KIO::Job *, const QByteArray &) ), mFolder, SLOT( slotSimpleData(KIO::Job *, const QByteArray &) ) ); connect( simpleJob, SIGNAL(infoMessage(KIO::Job *, const QString &)), SLOT(slotPutMessageInfoData(KIO::Job *, const QString &)) ); } //----------------------------------------------------------------------------- // TODO: port to KIO::StoredTransferJob once it's ok to require kdelibs-3.3 void CachedImapJob::slotPutMessageDataReq(KIO::Job *job, QByteArray &data) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if ((*it).data.size() - (*it).offset > 0x8000) { data.duplicate((*it).data.data() + (*it).offset, 0x8000); (*it).offset += 0x8000; } else if ((*it).data.size() - (*it).offset > 0) { data.duplicate((*it).data.data() + (*it).offset, (*it).data.size() - (*it).offset); (*it).offset = (*it).data.size(); } else data.resize(0); } //---------------------------------------------------------------------------- void CachedImapJob::slotPutMessageInfoData(KIO::Job *job, const QString &data) { KMFolderCachedImap * imapFolder = static_cast(mDestFolder->storage()); KMAcctCachedImap *account = imapFolder->account(); ImapAccountBase::JobIterator it = account->findJob( job ); if ( it == account->jobsEnd() ) return; if ( data.find("UID") != -1 && mMsg ) { int uid = (data.right(data.length()-4)).toInt(); kdDebug( 5006 ) << k_funcinfo << "Server told us uid is: " << uid << endl; mMsg->setUID( uid ); } } //----------------------------------------------------------------------------- void CachedImapJob::slotPutMessageResult(KIO::Job *job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if ( job->error() ) { bool cont = mAccount->handlePutError( job, *it, mFolder->folder() ); if ( !cont ) { delete this; } else { mMsg = 0; slotPutNextMessage(); } return; } emit messageStored( mMsg ); // we abuse those fields, the unit is the number of messages, here ++mSentBytes; emit progress( mSentBytes, mTotalBytes ); int i; if( ( i = mFolder->find(mMsg) ) != -1 ) { /* * If we have aquired a uid during upload the server supports the uidnext * extension and there is no need to redownload this mail, we already have * it. Otherwise remove it, it will be redownloaded. */ if ( mMsg->UID() == 0 ) { mFolder->removeMsg(i); } else { // When removing+readding, no point in telling the imap resources about it bool b = kmkernel->iCalIface().isResourceQuiet(); kmkernel->iCalIface().setResourceQuiet( true ); - mFolder->take( i ); + mFolder->takeTemporarily( i ); mFolder->addMsgKeepUID( mMsg ); mMsg->setTransferInProgress( false ); kmkernel->iCalIface().setResourceQuiet( b ); } } mMsg = NULL; mAccount->removeJob( it ); slotPutNextMessage(); } void CachedImapJob::slotAddNextSubfolder( KIO::Job * job ) { if (job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } // make copy of setting, to reset it before potentially destroying 'it' bool silentUpload = static_cast((*it).parent->storage())->silentUpload(); static_cast((*it).parent->storage())->setSilentUpload( false ); if ( job->error() && !silentUpload ) { QString myError = "

" + i18n("Error while uploading folder") + "

" + i18n("Could not make the folder %1 on the server.").arg((*it).items[0]) + "

" + i18n("This could be because you do not have permission to do this, or because the folder is already present on the server; the error message from the server communication is here:") + "

"; mAccount->handleJobError( job, myError ); } if( job->error() ) { delete this; return; } else { KMFolderCachedImap* storage = static_cast( (*it).current->storage() ); KMFolderCachedImap* parentStorage = static_cast( (*it).parent->storage() ); Q_ASSERT( storage ); Q_ASSERT( parentStorage ); if ( storage->imapPath().isEmpty() ) { QString path = mAccount->createImapPath( parentStorage->imapPath(), storage->folder()->name() ); if ( !storage->imapPathForCreation().isEmpty() ) path = storage->imapPathForCreation(); storage->setImapPath( path ); storage->writeConfig(); } } mAccount->removeJob( it ); } if (mFolderList.isEmpty()) { // No more folders to add delete this; return; } KMFolderCachedImap *folder = mFolderList.front(); mFolderList.pop_front(); KURL url = mAccount->getUrl(); QString path = mAccount->createImapPath( mFolder->imapPath(), folder->folder()->name() ); if ( !folder->imapPathForCreation().isEmpty() ) { // the folder knows it's namespace path = folder->imapPathForCreation(); } url.setPath( path ); if ( mAccount->groupwareType() != KMAcctCachedImap::GroupwareScalix ) { // Associate the jobData with the parent folder, not with the child // This is necessary in case of an error while creating the subfolder, // so that folderComplete is called on the parent (and the sync resetted). ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.items << folder->label(); // for the err msg jd.current = folder->folder(); KIO::SimpleJob *simpleJob = KIO::mkdir(url); KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob); mAccount->insertJob(simpleJob, jd); connect( simpleJob, SIGNAL(result(KIO::Job *)), this, SLOT(slotAddNextSubfolder(KIO::Job *)) ); } else { QByteArray packedArgs; QDataStream stream( packedArgs, IO_WriteOnly ); const QString command = QString( "X-CREATE-SPECIAL" ); const QString argument = QString( "%1 %2" ).arg( Scalix::Utils::contentsTypeToScalixId( folder->contentsType() ) ) .arg( path ); stream << (int) 'X' << 'N' << command << argument; ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.items << folder->label(); // for the err msg jd.current = folder->folder(); KIO::SimpleJob *simpleJob = KIO::special( url.url(), packedArgs, false ); KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob); mAccount->insertJob(simpleJob, jd); connect( simpleJob, SIGNAL(result(KIO::Job *)), this, SLOT(slotAddNextSubfolder(KIO::Job *)) ); } } void CachedImapJob::slotDeleteNextFolder( KIO::Job *job ) { if (job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } mAccount->removeDeletedFolder( (*it).path ); if( job->error() ) { mAccount->handleJobError( job, i18n( "Error while deleting folder %1 on the server: " ).arg( (*it).path ) + '\n' ); delete this; return; } mAccount->removeJob(it); } if( mFoldersOrMessages.isEmpty() ) { // No more folders to delete delete this; return; } QString folderPath = mFoldersOrMessages.front(); mFoldersOrMessages.pop_front(); KURL url = mAccount->getUrl(); url.setPath(folderPath); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.path = url.path(); KIO::SimpleJob *simpleJob = KIO::file_delete(url, false); KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob); mAccount->insertJob(simpleJob, jd); connect( simpleJob, SIGNAL( result(KIO::Job *) ), SLOT( slotDeleteNextFolder(KIO::Job *) ) ); } void CachedImapJob::checkUidValidity() { KURL url = mAccount->getUrl(); url.setPath( mFolder->imapPath() + ";UID=0:0" ); ImapAccountBase::jobData jd( url.url(), mFolder->folder() ); jd.cancellable = true; KIO::SimpleJob *job = KIO::get( url, false, false ); KIO::Scheduler::assignJobToSlave( mAccount->slave(), job ); mAccount->insertJob( job, jd ); connect( job, SIGNAL(result(KIO::Job *)), SLOT(slotCheckUidValidityResult(KIO::Job *)) ); connect( job, SIGNAL(data(KIO::Job *, const QByteArray &)), mFolder, SLOT(slotSimpleData(KIO::Job *, const QByteArray &))); } void CachedImapJob::slotCheckUidValidityResult(KIO::Job * job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if( job->error() ) { mErrorCode = job->error(); mAccount->handleJobError( job, i18n( "Error while reading folder %1 on the server: " ).arg( (*it).parent->label() ) + '\n' ); delete this; return; } // Check the uidValidity QCString cstr((*it).data.data(), (*it).data.size() + 1); int a = cstr.find("X-uidValidity: "); if (a < 0) { // Something is seriously rotten here! // TODO: Tell the user that he has a problem kdDebug(5006) << "No uidvalidity available for folder " << mFolder->name() << endl; } else { int b = cstr.find("\r\n", a); if ( (b - a - 15) >= 0 ) { QString uidv = cstr.mid(a + 15, b - a - 15); // kdDebug(5006) << "New uidv = " << uidv << ", old uidv = " // << mFolder->uidValidity() << endl; if( !mFolder->uidValidity().isEmpty() && mFolder->uidValidity() != uidv ) { // kdDebug(5006) << "Expunging the mailbox " << mFolder->name() // << "!" << endl; mFolder->expunge(); mFolder->setLastUid( 0 ); mFolder->clearUidMap(); } } else kdDebug(5006) << "No uidvalidity available for folder " << mFolder->name() << endl; } a = cstr.find( "X-PermanentFlags: " ); if ( a < 0 ) { kdDebug(5006) << "no PERMANENTFLAGS response? assumming custom flags are not available" << endl; } else { int b = cstr.find( "\r\n", a ); if ( (b - a - 18) >= 0 ) { int flags = cstr.mid( a + 18, b - a - 18 ).toInt(); emit permanentFlags( flags ); } else { kdDebug(5006) << "PERMANENTFLAGS response broken, assumming custom flags are not available" << endl; } } mAccount->removeJob(it); delete this; } void CachedImapJob::renameFolder( const QString &newName ) { // Set the source URL KURL urlSrc = mAccount->getUrl(); urlSrc.setPath( mFolder->imapPath() ); // Set the destination URL - this is a bit trickier KURL urlDst = mAccount->getUrl(); QString imapPath( mFolder->imapPath() ); // Destination url = old imappath - oldname + new name imapPath.truncate( imapPath.length() - mFolder->folder()->name().length() - 1); imapPath += newName + '/'; urlDst.setPath( imapPath ); ImapAccountBase::jobData jd( newName, mFolder->folder() ); jd.path = imapPath; KIO::SimpleJob *simpleJob = KIO::rename( urlSrc, urlDst, false ); KIO::Scheduler::assignJobToSlave( mAccount->slave(), simpleJob ); mAccount->insertJob( simpleJob, jd ); connect( simpleJob, SIGNAL(result(KIO::Job *)), SLOT(slotRenameFolderResult(KIO::Job *)) ); } static void renameChildFolders( KMFolderDir* dir, const QString& oldPath, const QString& newPath ) { if( dir ) { KMFolderNode *node = dir->first(); while( node ) { if( !node->isDir() ) { KMFolderCachedImap* imapFolder = static_cast(static_cast(node)->storage()); if ( !imapFolder->imapPath().isEmpty() ) // Only rename folders that have been accepted by the server if( imapFolder->imapPath().find( oldPath ) == 0 ) { QString p = imapFolder->imapPath(); p = p.mid( oldPath.length() ); p.prepend( newPath ); imapFolder->setImapPath( p ); renameChildFolders( imapFolder->folder()->child(), oldPath, newPath ); } } node = dir->next(); } } } void CachedImapJob::slotRenameFolderResult( KIO::Job *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if( job->error() ) { // Error, revert label change QMap::ConstIterator renit = mAccount->renamedFolders().find( mFolder->imapPath() ); Q_ASSERT( renit != mAccount->renamedFolders().end() ); if ( renit != mAccount->renamedFolders().end() ) { mFolder->folder()->setLabel( (*renit).mOldLabel ); mAccount->removeRenamedFolder( mFolder->imapPath() ); } mAccount->handleJobError( job, i18n( "Error while trying to rename folder %1" ).arg( mFolder->label() ) + '\n' ); } else { // Okay, the folder seems to be renamed on the server, // now rename it on disk QString oldName = mFolder->name(); QString oldPath = mFolder->imapPath(); mAccount->removeRenamedFolder( oldPath ); mFolder->setImapPath( (*it).path ); mFolder->FolderStorage::rename( (*it).url ); if( oldPath.endsWith( "/" ) ) oldPath.truncate( oldPath.length() -1 ); QString newPath = mFolder->imapPath(); if( newPath.endsWith( "/" ) ) newPath.truncate( newPath.length() -1 ); renameChildFolders( mFolder->folder()->child(), oldPath, newPath ); kmkernel->dimapFolderMgr()->contentsChanged(); mAccount->removeJob(it); } delete this; } void CachedImapJob::slotListMessagesResult( KIO::Job * job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); if ( it == mAccount->jobsEnd() ) { // Shouldn't happen delete this; return; } if (job->error()) { mErrorCode = job->error(); mAccount->handleJobError( job, i18n( "Error while deleting messages on the server: " ) + '\n' ); } else mAccount->removeJob(it); delete this; } //----------------------------------------------------------------------------- void CachedImapJob::setParentFolder( const KMFolderCachedImap* parent ) { mParentFolder = const_cast( parent ); } } #include "cachedimapjob.moc" diff --git a/kmail/kmfoldercachedimap.cpp b/kmail/kmfoldercachedimap.cpp index 0b511eb2a1..57282f49d2 100644 --- a/kmail/kmfoldercachedimap.cpp +++ b/kmail/kmfoldercachedimap.cpp @@ -1,3074 +1,3078 @@ /** * 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. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "kmkernel.h" #include "kmfoldercachedimap.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 "kmfolderseldlg.h" #include "kmcommands.h" #include "kmmainwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "annotationjobs.h" #include "quotajobs.h" using namespace KMail; #include #define UIDCACHE_VERSION 1 #define MAIL_LOSS_DEBUGGING 0 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::null; // 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, const char* name ) : KDialogBase( Plain, i18n( "Troubleshooting IMAP Cache" ), Ok | Cancel, Cancel, parent, name, true ), rc( None ) { QFrame* page = plainPage(); QVBoxLayout *topLayout = new QVBoxLayout( page, 0 ); // spell "lose" correctly. but don't cause a fuzzy. 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 loose all your local changes for this folder " "and all its subfolders.

", "

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.

" ); topLayout->addWidget( new QLabel( txt, page ) ); mButtonGroup = new QButtonGroup( 0 ); mIndexButton = new QRadioButton( page ); mIndexButton->setText( i18n( "Rebuild &Index" ) ); mButtonGroup->insert( mIndexButton ); topLayout->addWidget( mIndexButton ); QHBox *hbox = new QHBox( page ); QLabel *scopeLabel = new QLabel( i18n( "Scope:" ), hbox ); scopeLabel->setEnabled( false ); mIndexScope = new QComboBox( hbox ); mIndexScope->insertItem( i18n( "Only current folder" ) ); mIndexScope->insertItem( i18n( "Current folder and all subfolders" ) ); mIndexScope->insertItem( i18n( "All folders of this account" ) ); mIndexScope->setEnabled( false ); topLayout->addWidget( hbox ); mCacheButton = new QRadioButton( page ); mCacheButton->setText( i18n( "Refresh &Cache" ) ); mButtonGroup->insert( mCacheButton ); topLayout->addWidget( mCacheButton ); enableButtonSeparator( true ); connect ( mIndexButton, SIGNAL(toggled(bool)), mIndexScope, SLOT(setEnabled(bool)) ); connect ( mIndexButton, SIGNAL(toggled(bool)), scopeLabel, SLOT(setEnabled(bool)) ); connect( mButtonGroup, SIGNAL( clicked( int ) ), SLOT( slotChanged() ) ); connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) ); enableButtonOK( false ); } int DImapTroubleShootDialog::run() { DImapTroubleShootDialog d; d.exec(); return d.rc; } void DImapTroubleShootDialog::slotChanged() { enableButtonOK( mButtonGroup->selected() != 0 ); } void DImapTroubleShootDialog::slotDone() { rc = None; if ( mIndexButton->isOn() ) rc = mIndexScope->currentItem(); else if ( mCacheButton->isOn() ) rc = RefreshCache; done( Ok ); } KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName ) : KMFolderMaildir( folder, aName ), mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ), mSubfolderState( imapNoInformation ), mIncidencesFor( IncForAdmins ), mSharedSeenFlags( false ), mIsSelected( false ), mCheckFlags( true ), mReadOnly( false ), mAccount( NULL ), uidMapDirty( true ), uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ), mFoundAnIMAPDigest( false ), mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ), /*mHoldSyncs( false ),*/ mFolderRemoved( false ), mRecurse( true ), mAnnotationFolderTypeChanged( false ), mIncidencesForChanged( false ), mSharedSeenFlagsChanged( false ), mStatusChangedLocally( false ), mPersonalNamespacesCheckDone( true ), mQuotaInfo(), mAlarmsBlocked( false ), mRescueCommandCount( 0 ), mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values) { setUidValidity(""); // if we fail to read a uid file but there is one, nuke it if ( readUidCache() == -1 ) { if ( QFile::exists( uidCacheLocation() ) ) { KMessageBox::error( 0, i18n( "The UID cache file for folder %1 could not be read. There " "could be a problem with file system permission, or it is corrupted." ).arg( folder->prettyURL() ) ); // try to unlink it, in case it was corruped. If it couldn't be read // because of permissions, this will fail, which is fine unlink( QFile::encodeName( uidCacheLocation() ) ); } } mProgress = 0; } KMFolderCachedImap::~KMFolderCachedImap() { if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() ); writeConfig(); } void KMFolderCachedImap::reallyDoClose( const char* owner ) { if( !mFolderRemoved ) { writeUidCache(); } KMFolderMaildir::reallyDoClose( owner ); } 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(); KConfigGroupSaver saver( config, "Folder-" + folder()->idString() ); if( mImapPath.isEmpty() ) mImapPath = config->readEntry( "ImapPath" ); if( QString( name() ).upper() == "INBOX" && mImapPath == "/INBOX/" ) { folder()->setLabel( i18n( "inbox" ) ); // for the icon folder()->setSystemFolder( true ); } mNoContent = config->readBoolEntry( "NoContent", false ); mReadOnly = config->readBoolEntry( "ReadOnly", false ); if ( !config->readEntry( "FolderAttributes" ).isEmpty() ) mFolderAttributes = config->readEntry( "FolderAttributes" ); if ( mAnnotationFolderType != "FROMSERVER" ) { mAnnotationFolderType = config->readEntry( "Annotation-FolderType" ); // if there is an annotation, it has to be XML if ( !mAnnotationFolderType.isEmpty() && !mAnnotationFolderType.startsWith( "mail" ) ) kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML ); // kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath ) // << " readConfig: mAnnotationFolderType=" << mAnnotationFolderType << endl; } mIncidencesFor = incidencesForFromString( config->readEntry( "IncidencesFor" ) ); mAlarmsBlocked = config->readBoolEntry( "AlarmsBlocked", false ); // kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath ) // << " readConfig: mIncidencesFor=" << mIncidencesFor << endl; mSharedSeenFlags = config->readBoolEntry( "SharedSeenFlags", false ); mUserRights = config->readNumEntry( "UserRights", 0 ); // default is we don't know mOldUserRights = mUserRights; int storageQuotaUsage = config->readNumEntry( "StorageQuotaUsage", -1 ); int storageQuotaLimit = config->readNumEntry( "StorageQuotaLimit", -1 ); QString storageQuotaRoot = config->readEntry( "StorageQuotaRoot", QString::null ); 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 = config->readBoolEntry( "StatusChangedLocally", false ); QStringList uidsChanged = config->readListEntry( "UIDStatusChangedLocally" ); for ( QStringList::iterator it = uidsChanged.begin(); it != uidsChanged.end(); it++ ) { mUIDsOfLocallyChangedStatuses.insert( ( *it ).toUInt() ); } mAnnotationFolderTypeChanged = config->readBoolEntry( "AnnotationFolderTypeChanged", false ); mIncidencesForChanged = config->readBoolEntry( "IncidencesForChanged", false ); mSharedSeenFlagsChanged = config->readBoolEntry( "SharedSeenFlagsChanged", false ); if ( mImapPath.isEmpty() ) { mImapPathCreation = config->readEntry("ImapPathCreation"); } QStringList delUids = config->readListEntry( "UIDSDeletedSinceLastSync" ); #if MAIL_LOSS_DEBUGGING kdDebug( 5006 ) << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyURL() << endl << uids << endl; #endif for ( QStringList::iterator it = delUids.begin(); it != delUids.end(); it++ ) { mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0); } } void KMFolderCachedImap::writeConfig() { // don't re-write the config of a removed folder, this has just been deleted in // the folder manager if ( mFolderRemoved ) return; KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() ); configGroup.writeEntry( "ImapPath", mImapPath ); configGroup.writeEntry( "NoContent", mNoContent ); configGroup.writeEntry( "ReadOnly", mReadOnly ); configGroup.writeEntry( "FolderAttributes", mFolderAttributes ); // StatusChangedLocally is always false, as we use UIDStatusChangedLocally now configGroup.writeEntry( "StatusChangedLocally", false ); QStringList uidsToWrite; for( std::set::iterator it = mUIDsOfLocallyChangedStatuses.begin(); it != mUIDsOfLocallyChangedStatuses.end(); it++ ) { uidsToWrite.append( QString::number( (*it) ) ); } configGroup.writeEntry( "UIDStatusChangedLocally", uidsToWrite ); if ( !mImapPathCreation.isEmpty() ) { if ( mImapPath.isEmpty() ) { configGroup.writeEntry( "ImapPathCreation", mImapPathCreation ); } else { configGroup.deleteEntry( "ImapPathCreation" ); } } if ( !mDeletedUIDsSinceLastSync.isEmpty() ) { QValueList uids = mDeletedUIDsSinceLastSync.keys(); QStringList uidstrings; for( QValueList::iterator it = uids.begin(); it != uids.end(); it++ ) { uidstrings.append( QString::number( (*it) ) ); } configGroup.writeEntry( "UIDSDeletedSinceLastSync", uidstrings ); #if MAIL_LOSS_DEBUGGING kdDebug( 5006 ) << "WRITING OUT UIDSDeletedSinceLastSync in: " << folder( )->prettyURL( ) << endl << uidstrings << endl; #endif } else { configGroup.deleteEntry( "UIDSDeletedSinceLastSync" ); } 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( "SharedSeenFlags", mSharedSeenFlags ); configGroup.writeEntry( "SharedSeenFlagsChanged", mSharedSeenFlagsChanged ); 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(name()); 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( IO_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).stripWhiteSpace() ); len = uidcache.readLine( buf, sizeof(buf) ); if( len > 0 ) { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Reading in last uid from cache: " << QString::fromLocal8Bit(buf).stripWhiteSpace() << " in " << folder()->prettyURL() << endl; #endif // load the last known highest uid from the on disk cache setLastUid( QString::fromLocal8Bit(buf).stripWhiteSpace().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() ) ) return unlink( QFile::encodeName( uidCacheLocation() ) ); return 0; } #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Writing out UID cache lastuid: " << lastUid() << " in: " << folder()->prettyURL() << endl; #endif QFile uidcache( uidCacheLocation() ); if( uidcache.open( IO_WriteOnly ) ) { QTextStream str( &uidcache ); str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl; str << uidValidity() << endl; str << lastUid() << endl; uidcache.flush(); if ( uidcache.status() == IO_Ok ) { fsync( uidcache.handle() ); /* this is probably overkill */ uidcache.close(); if ( uidcache.status() == IO_Ok ) return 0; } } KMessageBox::error( 0, i18n( "The UID cache file for folder %1 could not be written. There " "could be a problem with file system permission." ).arg( folder()->prettyURL() ) ); return -1; } void KMFolderCachedImap::reloadUidMap() { //kdDebug(5006) << "Reloading Uid Map " << endl; uidMap.clear(); open("reloadUdi"); for( int i = 0; i < count(); ++i ) { KMMsgBase *msg = getMsgBase( i ); if( !msg ) continue; ulong uid = msg->UID(); //kdDebug(5006) << "Inserting: " << i << " with uid: " << uid << endl; uidMap.insert( uid, i ); } close("reloadUdi"); uidMapDirty = false; } -/* Reimplemented from KMFolderMaildir */ KMMessage* KMFolderCachedImap::take(int idx) { uidMapDirty = true; rememberDeletion( idx ); return KMFolderMaildir::take(idx); } +void KMFolderCachedImap::takeTemporarily( int idx ) +{ + KMFolderMaildir::take( idx ); +} + // Add a message without clearing it's X-UID field. 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 ) { kdDebug(5006) << k_funcinfo << "open: " << rc << " of folder: " << label() << endl; return rc; } // Add the message rc = KMFolderMaildir::addMsg(msg, index_return); if( newMail && ( imapPath() == "/INBOX/" || ( (userRights() <= 0 || userRights() & ACLJobs::Administer) && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) ) { // This is a new message. Filter it - maybe bool filter = false; if ( GlobalSettings::filterSourceFolders().isEmpty() ) { if ( imapPath() == "/INBOX/" ) filter = true; } else { if ( GlobalSettings::filterSourceFolders().contains( folder()->id() ) ) filter = true; } if ( filter ) mAccount->processNewMsg( msg ); } return rc; } /* Reimplemented from KMFolderMaildir */ 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::rememberDeletion( int idx ) { KMMsgBase *msg = getMsgBase( idx ); assert(msg); long uid = msg->UID(); assert(uid>=0); mDeletedUIDsSinceLastSync.insert(uid, 0); kdDebug(5006) << "Explicit delete of UID " << uid << " at index: " << idx << " in " << folder()->prettyURL() << endl; } /* Reimplemented from KMFolderMaildir */ void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet) { uidMapDirty = true; rememberDeletion( idx ); // 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; #if 0 // No special condition here, so let base class decide return KMFolderMaildir::canRemoveFolder(); #endif return true; } /* Reimplemented from KMFolderDir */ int KMFolderCachedImap::rename( const QString& aName, KMFolderDir* /*aParent*/ ) { QString oldName = mAccount->renamedFolder( imapPath() ); if ( oldName.isEmpty() ) oldName = name(); if ( aName == oldName ) // Stupid user trying to rename it to it's old name :) return 0; if( account() == 0 || imapPath().isEmpty() ) { // I 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 ( name() != 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 ) { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Setting mLastUid to: " << uid << " in " << folder()->prettyURL() << endl; #endif mLastUid = uid; if( uidWriteTimer == -1 ) // Write in one minute uidWriteTimer = startTimer( 60000 ); } void KMFolderCachedImap::timerEvent( QTimerEvent* ) { killTimer( uidWriteTimer ); uidWriteTimer = -1; if ( writeUidCache() == -1 ) unlink( QFile::encodeName( uidCacheLocation() ) ); } 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 MAIL_LOSS_DEBUGGING kdDebug(5006) << "Folder: " << folder()->prettyURL() << endl; kdDebug(5006) << "UID " << uid << " is supposed to be in the map" << endl; kdDebug(5006) << "UID's index is to be " << *it << endl; kdDebug(5006) << "There is a message there? " << (msg != 0) << endl; if ( msg ) { kdDebug(5006) << "Its UID is: " << msg->UID() << endl; } #endif if( msg && msg->UID() == uid ) return msg; kdDebug(5006) << "########## Didn't find uid: " << uid << "in cache athough it's supposed to be there!" << endl; } else { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Didn't find uid: " << uid << "in cache!" << endl; #endif } // 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 ); #if MAIL_LOSS_DEBUGGING else kdDebug(5006) << "Reloaded, but stil didn't find uid: " << uid << endl; #endif // Then it's not here return 0; } // This finds and sets the proper account for this folder if it has // not been done KMAcctCachedImap *KMFolderCachedImap::account() const { if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) { // Find the account mAccount = static_cast( kmkernel->acctMgr()->findByName( name() ) ); } 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.").arg( label() ); QString s1 = i18n("Refresh IMAP Cache"); QString s2 = i18n("&Refresh"); if( KMessageBox::warningContinueCancel( 0, str, s1, 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?" ).arg( imapPath() ).arg( mSyncState ), QString::null, i18n("Reset && Sync"), KStdGuiItem::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; #if 0 if( mHoldSyncs ) { // All done for this folder. account()->mailCheckProgressItem()->setProgress( 100 ); mProgress = 100; // all done newState( mProgress, i18n("Synchronization skipped")); mSyncState = SYNC_STATE_INITIAL; emit folderComplete( this, true ); return; } #endif 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; } //kdDebug(5006) << label() << ": " << state2String( mSyncState ) << endl; 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 // kdDebug(5006) << "makeConnection said Error, aborting." << endl; // We stop here. We're already in SYNC_STATE_INITIAL for the next time. newState( mProgress, i18n( "Error connecting to server %1" ).arg( mAccount->host() ) ); close("cachedimap"); emit folderComplete(this, false); break; } else if ( cs == ImapAccountBase::Connecting ) { mAccount->setAnnotationCheckPassed( false ); // kdDebug(5006) << "makeConnection said Connecting, waiting for signal." << endl; newState( mProgress, i18n("Connecting to %1").arg( 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 // kdDebug(5006) << "makeConnection said Connected, proceeding." << endl; mSyncState = SYNC_STATE_GET_USERRIGHTS; // Fall through to next state } } case SYNC_STATE_GET_USERRIGHTS: //kdDebug(5006) << "===== Syncing " << ( mImapPath.isEmpty() ? label() : mImapPath ) << endl; mSyncState = SYNC_STATE_RENAME_FOLDER; if( !noContent() && mAccount->hasACLSupport() ) { // Check the user's own 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 ( !mUIDsOfLocallyChangedStatuses.empty() || mStatusChangedLocally ) { uploadFlags(); break; } else { //kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl; } } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) { if ( !mUIDsOfLocallyChangedStatuses.empty() || 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::null, 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 highes uid seen thus far. */ slotUpdateLastUid(); if( mLastUid == 0 && uidWriteTimer == -1 ) { // This is probably a new and empty folder. Write the UID cache if ( writeUidCache() == -1 ) { resetSyncState(); emit folderComplete( this, false ); return; } } } } // 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() != "/" ) { kdDebug(5006) << "Setting test attribute on folder: "<< folder()->prettyURL() << endl; 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 ); kdDebug(5006) << "Setting test attribute to "<< url << endl; 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(KIO::Job *)), SLOT(slotTestAnnotationResult(KIO::Job *))); break; } case SYNC_STATE_GET_ANNOTATIONS: { #define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type" #define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for" #define KOLAB_SHAREDSEEN "/vendor/cmu/cyrus-imapd/sharedseen" //#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 = QString::null; } 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 ( !mSharedSeenFlagsChanged ) annotations << KOLAB_SHAREDSEEN; 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(KIO::Job *)), SLOT(slotGetAnnotationResult(KIO::Job *)) ); 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 ); kdDebug(5006) << "Setting folder-type annotation for " << label() << " to " << mAnnotationFolderType << endl; } if ( mIncidencesForChanged ) { const QString val = incidencesForToString( mIncidencesFor ); KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared", val ); annotations.append( attr ); kdDebug(5006) << "Setting incidences-for annotation for " << label() << " to " << val << endl; } if ( mSharedSeenFlagsChanged ) { const QString val = mSharedSeenFlags ? "true" : "false"; KMail::AnnotationAttribute attr( KOLAB_SHAREDSEEN, "value.shared", val ); annotations.append( attr ); kdDebug(5006) << k_funcinfo << "Setting sharedseen annotation for " << label() << " to " << val << endl; } 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(KIO::Job *)), SLOT(slotSetAnnotationResult(KIO::Job *))); 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(KIO::Job *)), SLOT(slotMultiSetACLResult(KIO::Job *))); 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(KIO::Job *)), SLOT(slotQuotaResult(KIO::Job *)) ); 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() ) { KMFolderNode *node = folder()->child()->first(); while( node ) { 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 { kdDebug(5006) << "Do not add " << storage->label() << " to synclist" << endl; } } node = folder()->child()->next(); } } // 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) ) ); //kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl; assert( !mCurrentSubfolder->imapPath().isEmpty() ); mCurrentSubfolder->setAccount( account() ); bool recurse = mCurrentSubfolder->noChildren() ? false : true; mCurrentSubfolder->serverSync( recurse ); } } break; default: kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state " << mSyncState << endl; } } /* Connected to the imap account's connectionResult signal. Emitted when the slave connected or failed to connect. */ 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) */ QValueList KMFolderCachedImap::findNewMessages() { QValueList result; 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 ) result.append( msg->getMsgSerNum() ); } return result; } /* Upload new messages to server */ void KMFolderCachedImap::uploadNewMessages() { QValueList 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.

").arg( folder()->prettyURL() ), i18n("Acces 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::null ); 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; if ( mUIDsOfLocallyChangedStatuses.find( msg->UID() ) == mUIDsOfLocallyChangedStatuses.end() && !mStatusChangedLocally ) { // This message has not had its status changed locally 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); } QMapIterator< QString, QStringList > dit; for( dit = groups.begin(); dit != groups.end(); ++dit ) { QCString flags = dit.key().latin1(); 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")); QValueList 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 ( mUIDsOfLocallyChangedStatuses.find( msg->UID() ) == mUIDsOfLocallyChangedStatuses.end() && !mStatusChangedLocally ) { // This message has not had its status changed locally continue; } if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead ) 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 ){ //kdDebug(5006) << "IMAP status changed but reset " << endl; return; // we were reset } //kdDebug(5006) << "IMAP status changed for folder: " << folder->prettyURL() << endl; 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(); //kdDebug(5006) << "Proceeding with mailcheck." << endl; } } } // This is not perfect, what if the status didn't really change? Oh well ... void KMFolderCachedImap::setStatus( int idx, KMMsgStatus status, bool toggle) { KMFolderMaildir::setStatus( idx, status, toggle ); const KMMsgBase *msg = getMsgBase( idx ); Q_ASSERT( msg ); if ( msg ) mUIDsOfLocallyChangedStatuses.insert( msg->UID() ); } void KMFolderCachedImap::setStatus(QValueList& ids, KMMsgStatus status, bool toggle) { KMFolderMaildir::setStatus(ids, status, toggle); for (QValueList::iterator it = ids.begin(); it != ids.end(); it++ ) { const KMMsgBase *msg = getMsgBase( *it ); Q_ASSERT( msg ); if ( msg ) mUIDsOfLocallyChangedStatuses.insert( msg->UID() ); } } /* Upload new folders to server */ void KMFolderCachedImap::createNewFolders() { QValueList newFolders = findNewFolders(); //kdDebug(5006) << label() << " createNewFolders:" << newFolders.count() << " new folders." << endl; 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(); } } QValueList KMFolderCachedImap::findNewFolders() { QValueList newFolders; if( folder() && folder()->child() ) { KMFolderNode *node = folder()->child()->first(); while( node ) { if( !node->isDir() ) { if( static_cast(node)->folderType() != KMFolderTypeCachedImap ) { kdError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! " << node->name() << " is not an IMAP folder\n"; node = folder()->child()->next(); assert(0); } KMFolderCachedImap* folder = static_cast(static_cast(node)->storage()); if( folder->imapPath().isEmpty() ) { newFolders << folder; } } node = folder()->child()->next(); } } return newFolders; } bool KMFolderCachedImap::deleteMessages() { /* Delete messages from cache that are gone from the server */ QPtrList 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 QStringList uids; QMap::const_iterator it = uidMap.constBegin(); for( ; it != uidMap.end(); it++ ) { ulong uid ( it.key() ); if( uid!=0 && !uidsOnServer.find( uid ) ) { uids << QString::number( uid ); msgsForDeletion.append( getMsg( *it ) ); } } if( !msgsForDeletion.isEmpty() ) { #if MAIL_LOSS_DEBUGGING if ( KMessageBox::warningYesNo( 0, i18n( "

Mails on the server in folder %1 were deleted. " "Do you want to delete them locally?
UIDs: %2

" ) .arg( folder()->prettyURL() ).arg( uids.join(",") ) ) == KMessageBox::Yes ) #endif removeMsg( msgsForDeletion ); } if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) ) return false; /* Delete messages from the server that we dont have anymore */ if( !uidsForDeletionOnServer.isEmpty() ) { newState( mProgress, i18n("Deleting removed messages from server")); QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true ); uidsForDeletionOnServer.clear(); kdDebug(5006) << "Deleting " << sets.count() << " sets of messages from server folder " << imapPath() << endl; 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; } else { // deleting on the server went fine, clear the pending deletions cache mDeletedUIDsSinceLastSync.clear(); } 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; } /* This will only list the messages in a folder. No directory listing done*/ 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.resize( count() * 2 ); uidsForDeletionOnServer.clear(); mMsgsForDownload.clear(); mUidsForDownload.clear(); // listing is only considered successful if saw a syntactically correct imapdigest mFoundAnIMAPDigest = false; 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 kdDebug(5006) << "could not find job!?!?!" << endl; // 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 += QCString(data, data.size() + 1); int pos = (*it).cdata.find("\r\n--IMAPDIGEST"); if (pos > 0) { int a = (*it).cdata.find("\r\nX-uidValidity:"); if (a != -1) { int b = (*it).cdata.find("\r\n", a + 17); setUidValidity((*it).cdata.mid(a + 17, b - a - 17)); } a = (*it).cdata.find("\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.find("\r\n", a + 12); const QString access = (*it).cdata.mid(a + 12, b - a - 12); setReadOnly( access == "Read only" ); } (*it).cdata.remove(0, pos); mFoundAnIMAPDigest = true; } pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1); // Start with something largish when rebuilding the cache if ( uidsOnServer.size() == 0 ) uidsOnServer.resize( KMail::nextPrime( 2000 ) ); const int v = 42; 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 QCString& entry( (*it).cdata ); const int indexOfUID = entry.find("X-UID", 16); const int startOfUIDValue = indexOfUID + 7; const int indexOfLength = entry.find("X-Length", startOfUIDValue ); // we know length comes after UID const int startOfLengthValue = indexOfLength + 10; const int indexOfFlags = entry.find("X-Flags", startOfLengthValue ); // we know flags comes last const int startOfFlagsValue = indexOfFlags + 9; const int flags = entry.mid( startOfFlagsValue, entry.find( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt(); const ulong size = entry.mid( startOfLengthValue, entry.find( '\r', startOfLengthValue ) - startOfLengthValue ).toULong(); const ulong uid = entry.mid( startOfUIDValue, entry.find( '\r', startOfUIDValue ) - startOfUIDValue ).toULong(); const bool deleted = ( flags & 8 ); if ( !deleted ) { if( uid != 0 ) { if ( uidsOnServer.count() == uidsOnServer.size() ) { uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) ); //kdDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl; } uidsOnServer.insert( uid, &v ); } 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. */ // kdDebug(5006) << "KMFolderCachedImap::slotGetMessagesData() : folder "<prettyURL() << endl; kdDebug(5006) << "The missing UID: " << uid << ". It will be redownloaded " << endl; 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 ); } } // kdDebug(5006) << "message with uid " << uid << " found in the local cache. " << endl; } if ( uid > lastUid() || redownload ) { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should download it" << endl; #endif // 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 ) { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Setting the tentative highest UID to: " << uid << endl; #endif mTentativeHighestUid = uid; } } } (*it).cdata.remove(0, pos); (*it).done++; pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1); } } void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet ) { mProgress += 10; if ( !job->error() && !mFoundAnIMAPDigest ) { kdWarning(5006) << "######## Folderlisting did not complete, but there was no error! " "Aborting sync of folder: " << folder()->prettyURL() << endl; #if MAIL_LOSS_DEBUGGING kmkernel->emergencyExit( i18n("Folder listing failed in interesting ways." ) ); #endif } 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; mUIDsOfLocallyChangedStatuses.clear(); // we are up to date again mStatusChangedLocally = false; } } serverSyncInternal(); } void KMFolderCachedImap::slotProgress(unsigned long done, unsigned long total) { int progressSpan = 100 - 5 - mProgress; //kdDebug(5006) << "KMFolderCachedImap::slotProgress done=" << done << " total=" << total << "=> mProgress=" << mProgress + ( progressSpan * done ) / total << endl; // Progress info while retrieving new emails // (going from mProgress to mProgress+progressSpan) newState( mProgress + (progressSpan * done) / total, QString::null ); } void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount) { assert( aAccount->isA("KMAcctCachedImap") ); 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; for( KMFolderNode* node = folder()->child()->first(); node; node = folder()->child()->next() ) 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; kdDebug(5006) << "listNamespaces " << mNamespacesToList << endl; 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").arg(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; kdDebug(5006) << "slotCheckNamespace " << subfolderNames << ",remain=" << mNamespacesToCheck << endl; // 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 kdWarning(5006) << "slotCheckNamespace: ignoring empty folder!" << endl; return; } folder()->createChildFolder(); KMFolderNode *node = 0; for ( node = folder()->child()->first(); node; node = folder()->child()->next()) { if ( !node->isDir() && node->name() == name ) break; } if ( !subfolderNames.isEmpty() ) { if ( node ) { // folder exists so we have nothing to do - it will be listed later kdDebug(5006) << "found namespace folder " << name << endl; } else { // create folder kdDebug(5006) << "create namespace folder " << name << endl; 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 ) { kdDebug(5006) << "delete namespace folder " << name << endl; KMFolder* fld = static_cast(node); kmkernel->dimapFolderMgr()->remove( fld ); } } if ( mNamespacesToCheck == 0 ) { // all namespaces are done so continue with the next step serverSyncInternal(); } } // This lists the subfolders on the server // and (in slotListResult) takes care of folders that have been removed on the server 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 ); //kdDebug(5006) << label() << ": folderNames=" << folderNames << " folderPaths=" //<< folderPaths << " mimeTypes=" << folderMimeTypes << endl; mSubfolderNames = folderNames; mSubfolderPaths = folderPaths; mSubfolderMimeTypes = folderMimeTypes; mSubfolderState = imapFinished; mSubfolderAttributes = folderAttributes; kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl; folder()->createChildFolder(); KMFolderNode *node = folder()->child()->first(); bool root = ( this == mAccount->rootFolder() ); QPtrList toRemove; bool emptyList = ( root && mSubfolderNames.empty() ); if ( !emptyList ) { while (node) { if (!node->isDir() ) { KMFolderCachedImap *f = static_cast(static_cast(node)->storage()); if ( mSubfolderNames.findIndex(node->name()) == -1 ) { 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() ); kdDebug(5006) << node->name() << " isn't on the server. It has an imapPath -> delete it locally" << endl; } } else { // folder both local and on server //kdDebug(5006) << node->name() << " is on the server." << endl; /** * Store the folder attributes for every subfolder. */ int index = mSubfolderNames.findIndex( node->name() ); f->mFolderAttributes = folderAttributes[ index ]; } } else { //kdDebug(5006) << "skipping dir node:" << node->name() << endl; } node = folder()->child()->next(); } } for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() ) { rescueUnsyncedMessagesAndDeleteFolder( doomed ); } mProgress += 5; // just in case there is nothing to rescue slotRescueDone( 0 ); } // This synchronizes the local folders as needed (creation/deletion). No network communication here. 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; // create the INBOX for (node = folder()->child()->first(); node; node = folder()->child()->next()) if (!node->isDir() && node->name() == "INBOX") 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 ) { kdDebug(5006) << "KMFolderCachedImap::listDirectory2 - pass listing to " << parent->label() << endl; mSubfolderNames.clear(); } } // Find all subfolders present on server but not on disk QValueVector foldersNewOnServer; for (uint i = 0; i < mSubfolderNames.count(); i++) { // Find the subdir, if already present KMFolderCachedImap *f = 0; KMFolderNode *node = 0; for (node = folder()->child()->first(); node; node = folder()->child()->next()) if (!node->isDir() && node->name() == mSubfolderNames[i]) 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?

" ).arg( mSubfolderNames[i] ), QString::null, KStdGuiItem::del(), KStdGuiItem::cancel() ) == KMessageBox::Yes; } if ( locallyDeleted ) { kdDebug(5006) << subfolderPath << " was deleted locally => delete on server." << endl; foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath ); // grab all subsubfolders too } else { kdDebug(5006) << subfolderPath << " is a new folder on the server => create local cache" << endl; foldersNewOnServer.append( i ); } } else { // Folder found locally if( static_cast(node)->folderType() == KMFolderTypeCachedImap ) f = dynamic_cast(static_cast(node)->storage()); if( f ) { // kdDebug(5006) << "folder("<name()<<")->imapPath()=" << f->imapPath() // << "\nSetting imapPath " << mSubfolderPaths[i] << endl; // 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 ( uint i = 0; i < foldersNewOnServer.count(); ++i ) paths << mSubfolderPaths[ foldersNewOnServer[i] ]; AnnotationJobs::MultiUrlGetAnnotationJob* job = AnnotationJobs::multiUrlGetAnnotation( mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE ); ImapAccountBase::jobData jd( QString::null, folder() ); jd.cancellable = true; mAccount->insertJob(job, jd); connect( job, SIGNAL(result(KIO::Job *)), SLOT(slotMultiUrlGetAnnotationResult(KIO::Job *)) ); } else { createFoldersNewOnServerAndFinishListing( foldersNewOnServer ); } } void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const QValueVector foldersNewOnServer ) { for ( uint 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()); kdDebug(5006) << " ####### Locally creating folder " << mSubfolderNames[idx] <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]; kdDebug(5006) << " ####### Attributes: " << f->mFolderAttributes <dimapFolderMgr()->contentsChanged(); } else { kdDebug(5006) << "can't create folder " << mSubfolderNames[idx] <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() ) { KMFolderNode *node = folder()->child()->first(); // look for a better parent while ( node ) { if ( node->name() == parent ) { KMFolder* fld = static_cast(node); KMFolderCachedImap* imapFld = static_cast( fld->storage() ); return imapFld; } node = folder()->child()->next(); } } } return 0; } void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool success) { Q_UNUSED(sub); //kdDebug(5006) << label() << " slotSubFolderComplete: " << sub->label() << endl; 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(IO_WriteOnly | IO_Append); buff.writeBlock(data.data(), data.size()); buff.close(); } FolderJob* KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder, QString, const AttachmentStrategy* ) const { QPtrList msgList; msgList.append( msg ); CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast( folder->storage() ):0 ); job->setParentFolder( this ); return job; } FolderJob* KMFolderCachedImap::doCreateJob( QPtrList& 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(KIO::Job *job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(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 job->showErrorDialog(); else kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL ); if (mAccount->slave()) mAccount->removeJob(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 < 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 ) { //kdDebug() << k_funcinfo << folder() << " " << mProgress << " " << syncStatus << endl; 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 childs KMFolderNode* node; QPtrListIterator it( *folder()->child() ); for ( ; (node = it.current()); ) { ++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.find( '.' ); 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() ) == KMailICalIfaceImpl::StorageXML ) { newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType ); if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) ) newSubType = "default"; else if ( oldSubType != "default" ) newSubType = oldSubType; // preserve unknown subtypes, like drafts etc. } //kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: " << newType << " " << newSubType << endl; if ( newType != oldType || newSubType != oldSubType ) { mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString::null : "."+newSubType ); mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl; } // Ensure that further readConfig()s don't lose mAnnotationFolderType writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); } void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor ) { if ( mIncidencesFor != incfor ) { mIncidencesFor = incfor; mIncidencesForChanged = true; } } void KMFolderCachedImap::setSharedSeenFlags(bool b) { if ( mSharedSeenFlags != b ) { mSharedSeenFlags = b; mSharedSeenFlagsChanged = 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.find( '.' ); 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 //kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: found known type of annotation" << endl; if ( contentsType != ContentsTypeMail ) kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::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; kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: parent folder is " << folder()->parent()->owner()->idString() << " => truncating annotation to " << value << endl; } 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 ) { //kdDebug(5006) << "slotGetAnnotationResult: no known type of annotation found, will need to set it" << endl; // 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 //kdDebug(5006) << "slotGetAnnotationResult: no annotation found, will need to set it" << endl; mAnnotationFolderTypeChanged = true; } } else if ( entry == KOLAB_INCIDENCESFOR ) { if ( found ) { mIncidencesFor = incidencesForFromString( value ); Q_ASSERT( mIncidencesForChanged == false ); } } else if ( entry == KOLAB_SHAREDSEEN ) { if ( found ) { mSharedSeenFlags = value == "true"; } } } void KMFolderCachedImap::slotGetAnnotationResult( KIO::Job* 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 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." ).arg( mAccount->host() ) ); mAccount->setHasNoAnnotationSupport(); } else kdWarning(5006) << "slotGetAnnotationResult: " << job->errorString() << endl; } if (mAccount->slave()) mAccount->removeJob(job); mProgress += 2; serverSyncInternal(); } void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KIO::Job* 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 QValueVector 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 doesn't have support for imap annotations. The XML storage cannot be used on this server, please re-configure KMail differently" ).arg( mAccount->host() ) ); mAccount->setHasNoAnnotationSupport(); } else kdWarning(5006) << "slotGetMultiUrlAnnotationResult: " << job->errorString() << endl; } 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.data(); kdDebug(5006) << k_funcinfo << "Folder: " << folderPath << " has type: " << annotation << endl; // we're only interested in the main type QString type(annotation); int dot = annotation.find( '.' ); if ( dot != -1 ) type.truncate( dot ); type = type.simplifyWhiteSpace(); const int idx = mSubfolderPaths.findIndex( folderPath ); const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory"; if ( ( isNoContent && type.isEmpty() ) || ( !type.isEmpty() && type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) { folders.append( idx ); kdDebug(5006) << k_funcinfo << " subscribing to: " << folderPath << endl; } else { kdDebug(5006) << k_funcinfo << " automatically unsubscribing from: " << folderPath << endl; mAccount->changeLocalSubscription( folderPath, false ); } } } if (mAccount->slave()) mAccount->removeJob(job); createFoldersNewOnServerAndFinishListing( folders ); } void KMFolderCachedImap::slotQuotaResult( KIO::Job* 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 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 kdWarning(5006) << "slotGetQuotaResult: " << job->errorString() << endl; } if (mAccount->slave()) mAccount->removeJob(job); mProgress += 2; serverSyncInternal(); } void KMFolderCachedImap::slotAnnotationChanged( const QString& entry, const QString& attribute, const QString& value ) { Q_UNUSED( attribute ); Q_UNUSED( value ); //kdDebug(5006) << k_funcinfo << entry << " " << attribute << " " << value << endl; 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(), KMailICalIfaceImpl::ACL ); } else if ( entry == KOLAB_SHAREDSEEN ) { mSharedSeenFlagsChanged = false; } } void KMFolderCachedImap::slotTestAnnotationResult(KIO::Job *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 mAccount->setAnnotationCheckPassed( true ); if ( job->error() ) { kdDebug(5006) << "Test Annotation was not passed, disabling annotation support" << endl; mAccount->setHasNoAnnotationSupport( ); } else { kdDebug(5006) << "Test Annotation was passed OK" << endl; } if (mAccount->slave()) mAccount->removeJob(job); serverSyncInternal(); } void KMFolderCachedImap::slotSetAnnotationResult(KIO::Job *job) { KMAcctCachedImap::JobIterator it = mAccount->findJob(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(job); } else { cont = mAccount->handleJobError( job, i18n( "Error while setting annotation: " ) + '\n' ); } } else { if (mAccount->slave()) mAccount->removeJob(job); } if ( cont ) serverSyncInternal(); } void KMFolderCachedImap::slotUpdateLastUid() { if( mTentativeHighestUid != 0 ) { // Sanity checking: // By now all new mails should be downloaded, which means // that iteration over the folder should yield only UIDs // lower or equal to what we think the highes ist, and the // highest one as well. If not, our notion of the highest // uid we've seen thus far is wrong, which is dangerous, so // don't update the mLastUid, then. // Not entirely true though, mails might have been moved out // of the folder already by filters, thus giving us a higher tentative // uid than we actually observe here. bool sane = count() == 0; for (int i=0;iUID(); if ( uid > mTentativeHighestUid && uid > lastUid() ) { kdWarning(5006) << "DANGER: Either the server listed a wrong highest uid, " "or we parsed it wrong. Send email to adam@kde.org, please, and include this log." << endl; kdWarning(5006) << "uid: " << uid << " mTentativeHighestUid: " << mTentativeHighestUid << endl; assert( false ); break; } else { sane = true; } } if (sane) { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "Tentative highest UID test was sane, writing out: " << mTentativeHighestUid << endl; #endif 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; KMFolderNode *node = 0; for( QPtrListIterator it( *folder()->child() ); (node = it.current()); ++it ) { if( !node->isDir() ) { KMFolderCachedImap* storage = static_cast(static_cast(node)->storage()); kdDebug() << k_funcinfo << "Re-indexing: " << storage->folder()->label() << endl; 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(); //kdDebug(5006) << "Quota ratio: " << ratio << "% " << mQuotaInfo.toString() << endl; closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() ); } //kdDebug(5006) << "Folder: " << folder()->prettyURL() << " is over quota: " << closeToQuota << endl; return closeToQuota; } KMCommand* KMFolderCachedImap::rescueUnsyncedMessages() { QValueList newMsgs = findNewMessages(); kdDebug() << k_funcinfo << newMsgs << " of " << count() << endl; 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 ) { kdWarning(5006) << k_funcinfo << "inbox not found!" << endl; 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") )) ) { kdDebug(5006) << k_funcinfo << "creating lost+found folder" << endl; 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 { kdDebug(5006) << k_funcinfo << "found lost+found folder" << endl; 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 ); } kdDebug(5006) << k_funcinfo << "creating lost+found folder " << name << endl; 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.

").arg( folder()->prettyURL() ).arg( 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?

").arg( folder()->prettyURL() ) ); if ( KMessageBox::warningYesNo( 0, msg, QString::null, i18n("Move"), i18n("Do Not Move") ) == KMessageBox::Yes ) { KMail::KMFolderSelDlg dlg( kmkernel->getKMMainWidget(), i18n("Move Messages to Folder"), true ); if ( dlg.exec() ) { dest = dlg.folder(); } } } if ( dest ) { QPtrList 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 ) { kdDebug() << k_funcinfo << folder << " " << root << endl; if ( root ) mToBeDeletedAfterRescue.append( folder ); folder->open("cachedimap"); 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("cachedimap"); } } if ( folder->child() ) { KMFolderNode *node = folder->child()->first(); while (node) { if (!node->isDir() ) { KMFolder *subFolder = static_cast( node ); rescueUnsyncedMessagesAndDeleteFolder( subFolder, false ); } node = folder->child()->next(); } } } void KMFolderCachedImap::slotRescueDone(KMCommand * command) { // FIXME: check command result if ( command ) --mRescueCommandCount; if ( mRescueCommandCount > 0 ) return; for ( QValueList::ConstIterator it = mToBeDeletedAfterRescue.constBegin(); it != mToBeDeletedAfterRescue.constEnd(); ++it ) { kmkernel->dimapFolderMgr()->remove( *it ); } mToBeDeletedAfterRescue.clear(); serverSyncInternal(); } bool KMFolderCachedImap::canDeleteMessages() const { if ( isReadOnly() ) return false; if ( userRights() > 0 && !(userRights() & ACLJobs::Delete) ) return false; return true; } #include "kmfoldercachedimap.moc" diff --git a/kmail/kmfoldercachedimap.h b/kmail/kmfoldercachedimap.h index 291f4950e5..3eb00d16f8 100644 --- a/kmail/kmfoldercachedimap.h +++ b/kmail/kmfoldercachedimap.h @@ -1,577 +1,589 @@ /* * 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. */ #ifndef kmfoldercachedimap_h #define kmfoldercachedimap_h #include #include #include #include #include #include #include "kmfoldermaildir.h" #include "kmfolderimap.h" #include "kmacctcachedimap.h" #include "kmfoldertype.h" #include "folderjob.h" #include "cachedimapjob.h" #include "quotajobs.h" #include using KMail::FolderJob; using KMail::QuotaInfo; class KMCommand; class QComboBox; class QRadioButton; namespace KMail { class AttachmentStrategy; class ImapAccountBase; struct ACLListEntry; } using KMail::AttachmentStrategy; class DImapTroubleShootDialog : public KDialogBase { Q_OBJECT public: enum SelectedOperation { None = -1, ReindexCurrent = 0, ReindexRecursive = 1, ReindexAll = 2, RefreshCache }; DImapTroubleShootDialog( QWidget* parent=0, const char* name=0 ); static int run(); private slots: void slotDone(); void slotChanged(); private: QRadioButton *mIndexButton, *mCacheButton; QComboBox *mIndexScope; QButtonGroup *mButtonGroup; int rc; }; class KMFolderCachedImap : public KMFolderMaildir { Q_OBJECT public: static QString cacheLocation() { return locateLocal("data", "kmail/dimap" ); } /** Usually a parent is given. But in some cases there is no fitting parent object available. Then the name of the folder is used as the absolute path to the folder file. */ KMFolderCachedImap(KMFolder* folder, const char* name=0); virtual ~KMFolderCachedImap(); /** @reimpl */ void reallyDoClose(const char* owner); /** Initialize this storage from another one. Used when creating a child folder */ void initializeFrom( KMFolderCachedImap* parent ); virtual void readConfig(); virtual void writeConfig(); void writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); /** Returns the type of this folder */ virtual KMFolderType folderType() const { return KMFolderTypeCachedImap; } /** @reimpl */ virtual int create(); /** Remove this folder */ virtual void remove(); /** Synchronize this folder and it's subfolders with the server */ virtual void serverSync( bool recurse ); /** Force the sync state to be done. */ void resetSyncState( ); /** Block this folder from generating alarms, even if the annotations * on it say otherwise. Used to override alarms for read-only folders. * (Only useful for resource folders) */ void setAlarmsBlocked( bool blocked ); /** Should alarms for this folder be blocked? (Only useful for resource folders) */ bool alarmsBlocked() const; void checkUidValidity(); enum imapState { imapNoInformation=0, imapInProgress=1, imapFinished=2 }; virtual imapState getContentState() { return mContentState; } virtual void setContentState(imapState state) { mContentState = state; } virtual imapState getSubfolderState() { return mSubfolderState; } virtual void setSubfolderState(imapState state); /** The path to the imap folder on the server */ void setImapPath(const QString &path); QString imapPath() const { return mImapPath; } /** The highest UID in the folder */ void setLastUid( ulong uid ); ulong lastUid(); /** Find message by UID. Returns NULL if it doesn't exist */ KMMsgBase* findByUID( ulong uid ); /** The uidvalidity of the last update */ void setUidValidity(const QString &validity) { mUidValidity = validity; } QString uidValidity() const { return mUidValidity; } /** Forget which mails are considered locally present. Needed when uidvalidity * changes. */ void clearUidMap() { uidMap.clear(); } /** The imap account associated with this folder */ void setAccount(KMAcctCachedImap *acct); KMAcctCachedImap* account() const; /** Returns the filename of the uidcache file */ QString uidCacheLocation() const; /** Read the uidValitidy and lastUid values from disk */ int readUidCache(); /** Write the uidValitidy and lastUid values to disk */ int writeUidCache(); /** Current progress status (between 0 and 100) */ int progress() const { return mProgress; } /* Reimplemented from KMFolder. Moving is not supported, so aParent must be 0 */ virtual int rename(const QString& aName, KMFolderDir *aParent=0); - /* Reimplemented from KMFolderMaildir */ + /** + * Reimplemented from KMFolderMaildir + * This deletes the message permanently, also from the server. For this, rememberDeletion() is + * called, so that the message can be deleted from the server on the next sync. + */ virtual KMMessage* take(int idx); + + /** + * Like take(), only that the deletion is not remembered, i.e. the message will not be deleted + * from the server. + * Calling this can cause inconsistencies, so make sure you re-add the message later! + */ + void takeTemporarily( int idx ); + /* Reimplemented from KMFolderMaildir */ virtual int addMsg(KMMessage* msg, int* index_return = 0); /* internal version that doesn't remove the X-UID header */ virtual int addMsgInternal(KMMessage* msg, bool, int* index_return = 0); virtual int addMsgKeepUID(KMMessage* msg, int* index_return = 0) { return addMsgInternal(msg, false, index_return); } /* Reimplemented from KMFolderMaildir */ virtual void removeMsg(int i, bool imapQuiet = false); virtual void removeMsg(QPtrList msgList, bool imapQuiet = false) { FolderStorage::removeMsg(msgList, imapQuiet); } /// Is the folder readonly? bool isReadOnly() const { return KMFolderMaildir::isReadOnly() || mReadOnly; } bool canDeleteMessages() const; /** * Emit the folderComplete signal */ void sendFolderComplete(bool success) { emit folderComplete(this, success); } /** * The silentUpload can be set to remove the folder upload error dialog */ void setSilentUpload( bool silent ) { mSilentUpload = silent; } bool silentUpload() { return mSilentUpload; } virtual int createIndexFromContents() { const int result = KMFolderMaildir::createIndexFromContents(); reloadUidMap(); return result; } int createIndexFromContentsRecursive(); //virtual void holdSyncs( bool hold ) { mHoldSyncs = hold; } /** * List a directory and add the contents to kmfoldermgr * It uses a ListJob to get the folders * returns false if the connection failed */ virtual bool listDirectory(); virtual void listNamespaces(); /** Return the trash folder. */ KMFolder* trashFolder() const; /** * The user's rights on this folder - see bitfield in ACLJobs namespace. * @return 0 when not known yet, -1 if there was an error fetching them */ int userRights() const { return mUserRights; } /// Set the user's rights on this folder - called by getUserRights void setUserRights( unsigned int userRights ); /** * The quota information for this folder. * @return an invalid info if we haven't synced yet, or the server * doesn't support quota. The difference can be figured out by * asking the account whether it supports quota. If we have * synced, the account supports quota, but there is no quota * on the folder, the return info will be valid, but empty. * @see QuotaInfo::isEmpty(), QuotaInfo::isValid() */ const QuotaInfo quotaInfo() const { return mQuotaInfo; } void setQuotaInfo( const QuotaInfo & ); /// Return the list of ACL for this folder typedef QValueVector ACLList; const ACLList& aclList() const { return mACLList; } /// Set the list of ACL for this folder (for FolderDiaACLTab) void setACLList( const ACLList& arr ); // Reimplemented so the mStatusChangedLocally bool can be set virtual void setStatus( int id, KMMsgStatus status, bool toggle ); virtual void setStatus( QValueList& ids, KMMsgStatus status, bool toggle ); QString annotationFolderType() const { return mAnnotationFolderType; } // For kmailicalifaceimpl only void updateAnnotationFolderType(); /// Free-busy and alarms relevance of this folder, i.e. for whom should /// events in this calendar lead to "busy" periods in their freebusy lists, /// and who should get alarms for the incidences in this folder. /// Applies to Calendar and Task folders only. /// /// IncForNobody: not relevant for free-busy and alarms to anybody /// IncForAdmins: apply to persons with admin permissions on this calendar /// IncForReaders: apply to all readers of this calendar enum IncidencesFor { IncForNobody, IncForAdmins, IncForReaders }; IncidencesFor incidencesFor() const { return mIncidencesFor; } /// For the folder properties dialog void setIncidencesFor( IncidencesFor incfor ); /** Returns wether the seen flag is shared among all users or every users has her own seen flags (default). */ bool sharedSeenFlags() const { return mSharedSeenFlags; } /** Enable shared seen flags (requires server support). */ void setSharedSeenFlags( bool b ); /** Returns true if this folder can be moved */ virtual bool isMoveable() const; /** * List of namespaces that need to be queried * Is set by the account for the root folder when the listing starts */ QStringList namespacesToList() { return mNamespacesToList; } void setNamespacesToList( QStringList list ) { mNamespacesToList = list; } /** * Specify an imap path that is used to create the folder on the server * Otherwise the parent folder is used to construct the path */ const QString& imapPathForCreation() { return mImapPathCreation; } void setImapPathForCreation( const QString& path ) { mImapPathCreation = path; } /** \reimp */ bool isCloseToQuota() const; /** Flags that can be permanently stored on the server. */ int permanentFlags() const { return mPermanentFlags; } QString folderAttributes() const { return mFolderAttributes; } protected slots: void slotGetMessagesData(KIO::Job * job, const QByteArray & data); void getMessagesResult(KMail::FolderJob *, bool lastSet); void slotGetLastMessagesResult(KMail::FolderJob *); void slotProgress(unsigned long done, unsigned long total); void slotPutProgress( unsigned long, unsigned long ); //virtual void slotCheckValidityResult(KIO::Job * job); void slotSubFolderComplete(KMFolderCachedImap*, bool); // Connected to the imap account void slotConnectionResult( int errorCode, const QString& errorMsg ); void slotCheckUidValidityResult( KMail::FolderJob* job ); void slotPermanentFlags( int flags ); void slotTestAnnotationResult(KIO::Job *job); void slotGetAnnotationResult( KIO::Job* ); void slotMultiUrlGetAnnotationResult( KIO::Job* ); void slotSetAnnotationResult(KIO::Job *job); void slotReceivedUserRights( KMFolder* ); void slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& ); void slotMultiSetACLResult(KIO::Job *); void slotACLChanged( const QString&, int ); void slotAnnotationResult(const QString& entry, const QString& value, bool found); void slotAnnotationChanged( const QString& entry, const QString& attribute, const QString& value ); void slotDeleteMessagesResult(KMail::FolderJob *); void slotImapStatusChanged(KMFolder* folder, const QString&, bool); void slotStorageQuotaResult( const QuotaInfo& ); void slotQuotaResult( KIO::Job* job ); protected: /* returns true if there were messages to delete on the server */ bool deleteMessages(); void listMessages(); void uploadNewMessages(); void uploadFlags(); void uploadSeenFlags(); void createNewFolders(); void listDirectory2(); void createFoldersNewOnServerAndFinishListing( const QValueVector foldersNewOnServer ); /** Utility methods for syncing. Finds new messages in the local cache that must be uploaded */ virtual QValueList findNewMessages(); /** Utility methods for syncing. Finds new subfolders in the local cache that must be created in the server */ virtual QValueList findNewFolders(); /** This returns false if we have subfolders. Otherwise it returns ::canRemoveFolder() */ virtual bool canRemoveFolder() const; /** Reimplemented from KMFolder */ virtual FolderJob* doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder, QString partSpecifier, const AttachmentStrategy *as ) const; virtual FolderJob* doCreateJob( QPtrList& msgList, const QString& sets, FolderJob::JobType jt, KMFolder *folder ) const; virtual void timerEvent( QTimerEvent* ); /* update progress status */ void newState( int progress, const QString& syncStatus ); /** See if there is a better parent then this folder */ KMFolderCachedImap* findParent( const QString& path, const QString& name ); public slots: /** * Add the data a KIO::Job retrieves to the buffer */ void slotSimpleData(KIO::Job * job, const QByteArray & data); /** * Troubleshoot the IMAP cache */ void slotTroubleshoot(); /** * Connected to ListJob::receivedFolders * creates/removes folders */ void slotListResult( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ); /** * Connected to ListJob::receivedFolders * creates namespace folders */ void slotCheckNamespace( const QStringList&, const QStringList&, const QStringList&, const QStringList&, const ImapAccountBase::jobData& ); private slots: void serverSyncInternal(); void slotIncreaseProgress(); void slotUpdateLastUid(); void slotFolderDeletionOnServerFinished(); void slotRescueDone( KMCommand* command ); signals: void folderComplete(KMFolderCachedImap *folder, bool success); void listComplete( KMFolderCachedImap* ); /** emitted when we enter the state "state" and have to process "number" items (for example messages */ void syncState( int state, int number ); private: void setReadOnly( bool readOnly ); QString state2String( int state ) const; void rememberDeletion( int ); /** Rescue not yet synced messages to a lost+found folder in case syncing is not possible because the folder has been deleted on the server or write access to this folder has been revoked. */ KMCommand* rescueUnsyncedMessages(); /** Recursive helper function calling the above method. */ void rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root = true ); /** State variable for the synchronization mechanism */ enum { SYNC_STATE_INITIAL, SYNC_STATE_TEST_ANNOTATIONS, SYNC_STATE_PUT_MESSAGES, SYNC_STATE_UPLOAD_FLAGS, SYNC_STATE_CREATE_SUBFOLDERS, SYNC_STATE_LIST_NAMESPACES, SYNC_STATE_LIST_SUBFOLDERS, SYNC_STATE_LIST_SUBFOLDERS2, SYNC_STATE_DELETE_SUBFOLDERS, SYNC_STATE_LIST_MESSAGES, SYNC_STATE_DELETE_MESSAGES, SYNC_STATE_EXPUNGE_MESSAGES, SYNC_STATE_GET_MESSAGES, SYNC_STATE_HANDLE_INBOX, SYNC_STATE_GET_USERRIGHTS, SYNC_STATE_GET_ANNOTATIONS, SYNC_STATE_SET_ANNOTATIONS, SYNC_STATE_GET_ACLS, SYNC_STATE_SET_ACLS, SYNC_STATE_GET_QUOTA, SYNC_STATE_FIND_SUBFOLDERS, SYNC_STATE_SYNC_SUBFOLDERS, SYNC_STATE_CHECK_UIDVALIDITY, SYNC_STATE_RENAME_FOLDER } mSyncState; int mProgress; int mStatusFlagsJobs; QString mUidValidity; QString mImapPath; imapState mContentState, mSubfolderState; QStringList mSubfolderNames, mSubfolderPaths, mSubfolderMimeTypes, mSubfolderAttributes; QString mFolderAttributes; QString mAnnotationFolderType; IncidencesFor mIncidencesFor; bool mSharedSeenFlags; bool mHasInbox; bool mIsSelected; bool mCheckFlags; bool mReadOnly; mutable QGuardedPtr mAccount; QIntDict uidsOnServer; QValueList uidsForDeletionOnServer; QValueList mMsgsForDownload; QValueList mUidsForDownload; QStringList foldersForDeletionOnServer; QValueList mSubfoldersForSync; KMFolderCachedImap* mCurrentSubfolder; /** Mapping uid -> index Keep updated in addMsg, take and removeMsg. This is used to lookup whether a mail is present locally or not. */ QMap uidMap; bool uidMapDirty; void reloadUidMap(); int uidWriteTimer; /** This is the last uid that we have seen from the server on the last sync. It is crucially important that this is correct at all times and not bumped up permaturely, as it is the watermark which is used to discern message which are not present locally, because they were deleted locally and now need to be deleted from the server, from those which are new and need to be downloaded. Sucessfull downloading of all pending mail from the server sets this. Between invocations it is stored on disk in the uidcache file. It must not change during a sync. */ ulong mLastUid; /** The highest id encountered while syncing. Once the sync process has successfully downloaded all pending mail and deleted on the server all messages that were removed locally, this will become the new mLastUid. See above for details. */ ulong mTentativeHighestUid; /** Used to determine whether listing messages yielded a sensible result. * Only then is the deletion o messages (which relies on succesful * listing) attempted, during the sync. */ bool mFoundAnIMAPDigest; int mUserRights, mOldUserRights; ACLList mACLList; bool mSilentUpload; bool mFolderRemoved; //bool mHoldSyncs; bool mRecurse; /// Set to true when the foldertype annotation needs to be set on the next sync bool mAnnotationFolderTypeChanged; /// Set to true when the "incidences-for" annotation needs to be set on the next sync bool mIncidencesForChanged; /// Set to true when the "sharedseen" annotation needs to be set on the next sync bool mSharedSeenFlagsChanged; /** * UIDs added by setStatus. Indicates that the client has changed * the status of those mails. The mail flags for changed mails will be * uploaded to the server, overwriting the server's notion of the status * of the mails in this folder. */ std::set mUIDsOfLocallyChangedStatuses; /** * Same as above, but uploads the flags of all mails, even if not all changed. * Only still here for config compatibility. */ bool mStatusChangedLocally; QStringList mNamespacesToList; int mNamespacesToCheck; bool mPersonalNamespacesCheckDone; QString mImapPathCreation; QuotaInfo mQuotaInfo; QMap mDeletedUIDsSinceLastSync; bool mAlarmsBlocked; QValueList mToBeDeletedAfterRescue; int mRescueCommandCount; int mPermanentFlags; }; #endif /*kmfoldercachedimap_h*/