/** * 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 TQt library by Trolltech AS, Norway (or with modified versions * of TQt that use the same license as TQt), 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 * TQt. 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, "mTimer" ), 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("mboxcompact"); if ( mTmpFile ) fclose( mTmpFile ); mTmpFile = 0; if ( !mTempName.isEmpty() ) TQFile::remove( mTempName ); FolderJob::kill(); } TQString MboxCompactionJob::realLocation() const { TQString location = mSrcFolder->location(); TQFileInfo 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()) { kdDebug(5006) << storage->location() << " compaction skipped." << endl; if ( !mSilent ) { TQString str = i18n( "For safety reasons, compaction has been disabled for %1" ).arg( mbox->label() ); BroadcastStatus::instance()->setStatusMsg( str ); } return 0; } kdDebug(5006) << "Compacting " << mSrcFolder->idString() << endl; if (KMFolderIndex::IndexOk != mbox->indexStatus()) { kdDebug(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 TQFileInfo 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.dirPath() + "/." + pathInfo.fileName() + ".compacted"; mode_t old_umask = umask(077); mTmpFile = fopen(TQFile::encodeName(mTempName), "w"); umask(old_umask); if (!mTmpFile) { kdWarning(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("mboxcompact"); mOpeningFolder = false; mFolderOpen = true; mOffset = 0; mCurrentIndex = 0; kdDebug(5006) << "MboxCompactionJob: starting to compact folder " << mSrcFolder->location() << " into " << mTempName << endl; connect( &mTimer, TQT_SIGNAL( timeout() ), TQT_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); TQString str; if (!rc) { bool autoCreate = mbox->autoCreateIndex(); TQString box( realLocation() ); ::rename(TQFile::encodeName(mTempName), TQFile::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" ).arg( mSrcFolder->label() ); kdDebug(5006) << str << endl; } else { mbox->close("mboxcompact"); str = i18n( "Error occurred while compacting \"%1\". Compaction aborted." ).arg( mSrcFolder->label() ); kdDebug(5006) << "Error occurred while compacting " << mbox->location() << endl; kdDebug(5006) << "Compaction aborted." << endl; TQFile::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, "mTimer" ), 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() ); kdDebug(5006) << "Compacting " << mSrcFolder->idString() << endl; mOpeningFolder = true; // Ignore open-notifications while opening the folder storage->open("maildircompact"); mOpeningFolder = false; mFolderOpen = true; TQString subdirNew(storage->location() + "/new/"); TQDir d(subdirNew); mEntryList = d.entryList(); mCurrentIndex = 0; kdDebug(5006) << "MaildirCompactionJob: starting to compact in folder " << mSrcFolder->location() << endl; connect( &mTimer, TQT_SIGNAL( timeout() ), TQT_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; TQString str; if ( !rc ) { str = i18n( "Folder \"%1\" successfully compacted" ).arg( mSrcFolder->label() ); } else { str = i18n( "Error occurred while compacting \"%1\". Compaction aborted." ).arg( 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"