/** * Copyright (c) 2004 David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "compactionjob.h" #include "kmfolder.h" #include "broadcaststatus.h" using KPIM::BroadcastStatus; #include "kmfoldermbox.h" #include "kmfoldermaildir.h" #include #include #include #include #include #include #include #include using namespace KMail; // Look at this number of messages in each slotDoWork call #define COMPACTIONJOB_NRMESSAGES 100 // And wait this number of milliseconds before calling it again #define COMPACTIONJOB_TIMERINTERVAL 100 MboxCompactionJob::MboxCompactionJob( KMFolder* folder, bool immediate ) : ScheduledJob( folder, immediate ), mTimer( this ), mTmpFile( 0 ), mCurrentIndex( 0 ), mFolderOpen( false ), mSilent( false ) { } MboxCompactionJob::~MboxCompactionJob() { } void MboxCompactionJob::kill() { Q_ASSERT( mCancellable ); // We must close the folder if we opened it and got interrupted if ( mFolderOpen && mSrcFolder && mSrcFolder->storage() ) { mSrcFolder->storage()->close( "mboxcompactjob" ); } if ( mTmpFile ) { fclose( mTmpFile ); } mTmpFile = 0; if ( !mTempName.isEmpty() ) { QFile::remove( mTempName ); } FolderJob::kill(); } QString MboxCompactionJob::realLocation() const { QString location = mSrcFolder->location(); QFileInfo inf( location ); if (inf.isSymLink()) { KUrl u; u.setPath( location ); // follow (and resolve) symlinks so that the final ::rename() always works // KUrl gives us support for absolute and relative links transparently. return KUrl( u, inf.readLink() ).path(); } return location; } int MboxCompactionJob::executeNow( bool silent ) { mSilent = silent; FolderStorage *storage = mSrcFolder->storage(); KMFolderMbox *mbox = static_cast( storage ); if ( !storage->compactable() ) { kDebug(5006) << storage->location() << " compaction skipped." << endl; if ( !mSilent ) { QString str = i18n( "For safety reasons, compaction has been disabled for %1", mbox->label() ); BroadcastStatus::instance()->setStatusMsg( str ); } return 0; } kDebug(5006) << "Compacting " << mSrcFolder->idString() << endl; if ( KMFolderIndex::IndexOk != mbox->indexStatus() ) { kDebug(5006) << "Critical error: " << storage->location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } const QFileInfo pathInfo( realLocation() ); // Use /dir/.mailboxname.compacted so that it's hidden, and doesn't show up after restarting kmail // (e.g. due to an unfortunate crash while compaction is happening) mTempName = pathInfo.path() + "/." + pathInfo.fileName() + ".compacted"; mode_t old_umask = umask( 077 ); mTmpFile = fopen( QFile::encodeName( mTempName ), "w" ); umask( old_umask ); if (!mTmpFile) { kWarning(5006) << "Couldn't start compacting " << mSrcFolder->label() << " : " << strerror( errno ) << " while creating " << mTempName << endl; return errno; } mOpeningFolder = true; // Ignore open-notifications while opening the folder storage->open( "mboxcompactjob" ); mOpeningFolder = false; mFolderOpen = true; mOffset = 0; mCurrentIndex = 0; kDebug(5006) << "MboxCompactionJob: starting to compact folder " << mSrcFolder->location() << " into " << mTempName << endl; connect( &mTimer, SIGNAL( timeout() ), SLOT( slotDoWork() ) ); if ( !mImmediate ) { mTimer.start( COMPACTIONJOB_TIMERINTERVAL ); } slotDoWork(); return mErrorCode; } void MboxCompactionJob::slotDoWork() { // No need to worry about mSrcFolder==0 here. The FolderStorage deletes the jobs on destruction. KMFolderMbox *mbox = static_cast( mSrcFolder->storage() ); bool bDone = false; int nbMessages = mImmediate ? -1 /*all*/ : COMPACTIONJOB_NRMESSAGES; int rc = mbox->compact( mCurrentIndex, nbMessages, mTmpFile, mOffset /*in-out*/, bDone /*out*/ ); if ( !mImmediate ) mCurrentIndex += COMPACTIONJOB_NRMESSAGES; if ( rc || bDone ) // error, or finished done( rc ); } void MboxCompactionJob::done( int rc ) { mTimer.stop(); mCancellable = false; KMFolderMbox *mbox = static_cast( mSrcFolder->storage() ); if ( !rc ) { rc = fflush( mTmpFile ); } if ( !rc ) { rc = fsync( fileno( mTmpFile ) ); } rc |= fclose( mTmpFile ); QString str; if ( !rc ) { bool autoCreate = mbox->autoCreateIndex(); QString box( realLocation() ); ::rename( QFile::encodeName( mTempName ), QFile::encodeName( box ) ); mbox->writeIndex(); mbox->writeConfig(); mbox->setAutoCreateIndex( false ); mbox->close( "mboxcompact", true ); mbox->setAutoCreateIndex( autoCreate ); mbox->setNeedsCompacting( false ); // We are clean now str = i18n( "Folder \"%1\" successfully compacted", mSrcFolder->label() ); kDebug(5006) << str << endl; } else { mbox->close( "mboxcompact" ); str = i18n( "Error occurred while compacting \"%1\". Compaction aborted.", mSrcFolder->label() ); kDebug(5006) << "Error occurred while compacting " << mbox->location() << endl; kDebug(5006) << "Compaction aborted." << endl; QFile::remove( mTempName ); } mErrorCode = rc; if ( !mSilent ) BroadcastStatus::instance()->setStatusMsg( str ); mFolderOpen = false; deleteLater(); // later, because of the "return mErrorCode" } //// MaildirCompactionJob::MaildirCompactionJob( KMFolder* folder, bool immediate ) : ScheduledJob( folder, immediate ), mTimer( this ), mCurrentIndex( 0 ), mFolderOpen( false ), mSilent( false ) { } MaildirCompactionJob::~MaildirCompactionJob() { } void MaildirCompactionJob::kill() { Q_ASSERT( mCancellable ); // We must close the folder if we opened it and got interrupted if ( mFolderOpen && mSrcFolder && mSrcFolder->storage() ) { mSrcFolder->storage()->close( "maildircompact" ); } FolderJob::kill(); } int MaildirCompactionJob::executeNow( bool silent ) { mSilent = silent; KMFolderMaildir *storage = static_cast( mSrcFolder->storage() ); kDebug(5006) << "Compacting " << mSrcFolder->idString() << endl; mOpeningFolder = true; // Ignore open-notifications while opening the folder storage->open( "maildircompact" ); mOpeningFolder = false; mFolderOpen = true; QString subdirNew( storage->location() + "/new/" ); QDir d( subdirNew ); mEntryList = d.entryList(); mCurrentIndex = 0; kDebug(5006) << "MaildirCompactionJob: starting to compact in folder " << mSrcFolder->location() << endl; connect( &mTimer, SIGNAL( timeout() ), SLOT( slotDoWork() ) ); if ( !mImmediate ) { mTimer.start( COMPACTIONJOB_TIMERINTERVAL ); } slotDoWork(); return mErrorCode; } void MaildirCompactionJob::slotDoWork() { // No need to worry about mSrcFolder==0 here. The FolderStorage deletes the jobs on destruction. KMFolderMaildir *storage = static_cast( mSrcFolder->storage() ); bool bDone = false; int nbMessages = mImmediate ? -1 /*all*/ : COMPACTIONJOB_NRMESSAGES; int rc = storage->compact( mCurrentIndex, nbMessages, mEntryList, bDone /*out*/ ); if ( !mImmediate ) mCurrentIndex += COMPACTIONJOB_NRMESSAGES; if ( rc || bDone ) // error, or finished done( rc ); } void MaildirCompactionJob::done( int rc ) { KMFolderMaildir *storage = static_cast( mSrcFolder->storage() ); mTimer.stop(); mCancellable = false; QString str; if ( !rc ) { str = i18n( "Folder \"%1\" successfully compacted", mSrcFolder->label() ); } else { str = i18n( "Error occurred while compacting \"%1\". Compaction aborted.", mSrcFolder->label() ); } mErrorCode = rc; storage->setNeedsCompacting( false ); storage->close( "maildircompact" ); if ( storage->isOpened() ) { storage->updateIndex(); } if ( !mSilent ) { BroadcastStatus::instance()->setStatusMsg( str ); } mFolderOpen = false; deleteLater(); // later, because of the "return mErrorCode" } ScheduledJob *ScheduledCompactionTask::run() { if ( !folder() || !folder()->needsCompacting() ) return 0; switch( folder()->storage()->folderType() ) { case KMFolderTypeMbox: return new MboxCompactionJob( folder(), isImmediate() ); case KMFolderTypeCachedImap: case KMFolderTypeMaildir: return new MaildirCompactionJob( folder(), isImmediate() ); default: // imap, search, unknown... return 0; } } #include "compactionjob.moc"