diff options
Diffstat (limited to 'kmail/kmfoldermaildir.cpp')
-rw-r--r-- | kmail/kmfoldermaildir.cpp | 1172 |
1 files changed, 1172 insertions, 0 deletions
diff --git a/kmail/kmfoldermaildir.cpp b/kmail/kmfoldermaildir.cpp new file mode 100644 index 000000000..f13bb6761 --- /dev/null +++ b/kmail/kmfoldermaildir.cpp @@ -0,0 +1,1172 @@ +// -*- mode: C++; c-file-style: "gnu" -*- +// kmfoldermaildir.cpp +// Author: Kurt Granroth <granroth@kde.org> + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <qdir.h> +#include <qregexp.h> + +#include <libkdepim/kfileio.h> +#include "kmfoldermaildir.h" +#include "kmfoldermgr.h" +#include "kmfolder.h" +#include "undostack.h" +#include "maildirjob.h" +#include "kcursorsaver.h" +#include "jobscheduler.h" +using KMail::MaildirJob; +#include "compactionjob.h" +#include "kmmsgdict.h" +#include "util.h" + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstaticdeleter.h> +#include <kmessagebox.h> +#include <kdirsize.h> + +#include <dirent.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <assert.h> +#include <limits.h> +#include <ctype.h> +#include <fcntl.h> + +#ifndef MAX_LINE +#define MAX_LINE 4096 +#endif +#ifndef INIT_MSGS +#define INIT_MSGS 8 +#endif + +// define the static member +QValueList<KMFolderMaildir::DirSizeJobQueueEntry> KMFolderMaildir::s_DirSizeJobQueue; + +//----------------------------------------------------------------------------- +KMFolderMaildir::KMFolderMaildir(KMFolder* folder, const char* name) + : KMFolderIndex(folder, name), mCurrentlyCheckingFolderSize(false) +{ + +} + + +//----------------------------------------------------------------------------- +KMFolderMaildir::~KMFolderMaildir() +{ + if (mOpenCount>0) close("~foldermaildir", true); + if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() ); +} + +//----------------------------------------------------------------------------- +int KMFolderMaildir::canAccess() +{ + + assert(!folder()->name().isEmpty()); + + QString sBadFolderName; + if (access(QFile::encodeName(location()), R_OK | W_OK | X_OK) != 0) { + sBadFolderName = location(); + } else if (access(QFile::encodeName(location() + "/new"), R_OK | W_OK | X_OK) != 0) { + sBadFolderName = location() + "/new"; + } else if (access(QFile::encodeName(location() + "/cur"), R_OK | W_OK | X_OK) != 0) { + sBadFolderName = location() + "/cur"; + } else if (access(QFile::encodeName(location() + "/tmp"), R_OK | W_OK | X_OK) != 0) { + sBadFolderName = location() + "/tmp"; + } + + if ( !sBadFolderName.isEmpty() ) { + int nRetVal = QFile::exists(sBadFolderName) ? EPERM : ENOENT; + KCursorSaver idle(KBusyPtr::idle()); + if ( nRetVal == ENOENT ) + KMessageBox::sorry(0, i18n("Error opening %1; this folder is missing.") + .arg(sBadFolderName)); + else + KMessageBox::sorry(0, i18n("Error opening %1; either this is not a valid " + "maildir folder, or you do not have sufficient access permissions.") + .arg(sBadFolderName)); + return nRetVal; + } + + return 0; +} + +//----------------------------------------------------------------------------- +int KMFolderMaildir::open(const char *) +{ + int rc = 0; + + mOpenCount++; + kmkernel->jobScheduler()->notifyOpeningFolder( folder() ); + + if (mOpenCount > 1) return 0; // already open + + assert(!folder()->name().isEmpty()); + + rc = canAccess(); + if ( rc != 0 ) { + return rc; + } + + if (!folder()->path().isEmpty()) + { + if (KMFolderIndex::IndexOk != indexStatus()) // test if contents file has changed + { + QString str; + mIndexStream = 0; + str = i18n("Folder `%1' changed; recreating index.") + .arg(name()); + emit statusMsg(str); + } else { + mIndexStream = fopen(QFile::encodeName(indexLocation()), "r+"); // index file + if ( mIndexStream ) { + fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); + updateIndexStreamPtr(); + } + } + + if (!mIndexStream) + rc = createIndexFromContents(); + else + readIndex(); + } + else + { + mAutoCreateIndex = false; + rc = createIndexFromContents(); + } + + mChanged = false; + + //readConfig(); + + return rc; +} + + +//----------------------------------------------------------------------------- +int KMFolderMaildir::createMaildirFolders( const QString & folderPath ) +{ + // Make sure that neither a new, cur or tmp subfolder exists already. + QFileInfo dirinfo; + dirinfo.setFile( folderPath + "/new" ); + if ( dirinfo.exists() ) return EEXIST; + dirinfo.setFile( folderPath + "/cur" ); + if ( dirinfo.exists() ) return EEXIST; + dirinfo.setFile( folderPath + "/tmp" ); + if ( dirinfo.exists() ) return EEXIST; + + // create the maildir directory structure + if ( ::mkdir( QFile::encodeName( folderPath ), S_IRWXU ) > 0 ) { + kdDebug(5006) << "Could not create folder " << folderPath << endl; + return errno; + } + if ( ::mkdir( QFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) { + kdDebug(5006) << "Could not create folder " << folderPath << "/new" << endl; + return errno; + } + if ( ::mkdir( QFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) { + kdDebug(5006) << "Could not create folder " << folderPath << "/cur" << endl; + return errno; + } + if ( ::mkdir( QFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) { + kdDebug(5006) << "Could not create folder " << folderPath << "/tmp" << endl; + return errno; + } + + return 0; // no error +} + +//----------------------------------------------------------------------------- +int KMFolderMaildir::create() +{ + int rc; + int old_umask; + + assert(!folder()->name().isEmpty()); + assert(mOpenCount == 0); + + rc = createMaildirFolders( location() ); + if ( rc != 0 ) + return rc; + + // FIXME no path == no index? - till + if (!folder()->path().isEmpty()) + { + old_umask = umask(077); + mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW + updateIndexStreamPtr(true); + umask(old_umask); + + if (!mIndexStream) return errno; + fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); + } + else + { + mAutoCreateIndex = false; + } + + mOpenCount++; + mChanged = false; + + rc = writeIndex(); + return rc; +} + + +//----------------------------------------------------------------------------- +void KMFolderMaildir::reallyDoClose(const char* owner) +{ + if (mAutoCreateIndex) + { + updateIndex(); + writeConfig(); + } + + mMsgList.clear(true); + + if (mIndexStream) { + fclose(mIndexStream); + updateIndexStreamPtr(true); + } + + mOpenCount = 0; + mIndexStream = 0; + mUnreadMsgs = -1; + + mMsgList.reset(INIT_MSGS); +} + +//----------------------------------------------------------------------------- +void KMFolderMaildir::sync() +{ + if (mOpenCount > 0) + if (!mIndexStream || fsync(fileno(mIndexStream))) { + kmkernel->emergencyExit( i18n("Could not sync maildir folder.") ); + } +} + +//----------------------------------------------------------------------------- +int KMFolderMaildir::expungeContents() +{ + // nuke all messages in this folder now + QDir d(location() + "/new"); + // d.setFilter(QDir::Files); coolo: QFile::remove returns false for non-files + QStringList files(d.entryList()); + QStringList::ConstIterator it(files.begin()); + for ( ; it != files.end(); ++it) + QFile::remove(d.filePath(*it)); + + d.setPath(location() + "/cur"); + files = d.entryList(); + for (it = files.begin(); it != files.end(); ++it) + QFile::remove(d.filePath(*it)); + + return 0; +} + +int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const QStringList& entryList, bool& done ) +{ + QString subdirNew(location() + "/new/"); + QString subdirCur(location() + "/cur/"); + + unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() : + QMIN( mMsgList.count(), startIndex + nbMessages ); + //kdDebug(5006) << "KMFolderMaildir: compacting from " << startIndex << " to " << stopIndex << endl; + for(unsigned int idx = startIndex; idx < stopIndex; ++idx) { + KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx); + if (!mi) + continue; + + QString filename(mi->fileName()); + if (filename.isEmpty()) + continue; + + // first, make sure this isn't in the 'new' subdir + if ( entryList.contains( filename ) ) + moveInternal(subdirNew + filename, subdirCur + filename, mi); + + // construct a valid filename. if it's already valid, then + // nothing happens + filename = constructValidFileName( filename, mi->status() ); + + // if the name changed, then we need to update the actual filename + if (filename != mi->fileName()) + { + moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi); + mi->setFileName(filename); + setDirty( true ); + } + +#if 0 + // we can't have any New messages at this point + if (mi->isNew()) + { + mi->setStatus(KMMsgStatusUnread); + setDirty( true ); + } +#endif + } + done = ( stopIndex == mMsgList.count() ); + return 0; +} + +//----------------------------------------------------------------------------- +int KMFolderMaildir::compact( bool silent ) +{ + KMail::MaildirCompactionJob* job = new KMail::MaildirCompactionJob( folder(), true /*immediate*/ ); + int rc = job->executeNow( silent ); + // Note that job autodeletes itself. + return rc; +} + +//------------------------------------------------------------- +FolderJob* +KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt, + KMFolder *folder, QString, const AttachmentStrategy* ) const +{ + MaildirJob *job = new MaildirJob( msg, jt, folder ); + job->setParentFolder( this ); + return job; +} + +//------------------------------------------------------------- +FolderJob* +KMFolderMaildir::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets, + FolderJob::JobType jt, KMFolder *folder ) const +{ + MaildirJob *job = new MaildirJob( msgList, sets, jt, folder ); + job->setParentFolder( this ); + return job; +} + +//------------------------------------------------------------- +int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return) +{ + if (!canAddMsgNow(aMsg, index_return)) return 0; + return addMsgInternal( aMsg, index_return ); +} + +//------------------------------------------------------------- +int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return, + bool stripUid ) +{ +/* +QFile fileD0( "testdat_xx-kmfoldermaildir-0" ); +if( fileD0.open( IO_WriteOnly ) ) { + QDataStream ds( &fileD0 ); + ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); + fileD0.close(); // If data is 0 we just create a zero length file. +} +*/ + long len; + unsigned long size; + KMFolder* msgParent; + QCString msgText; + int idx(-1); + int rc; + + // take message out of the folder it is currently in, if any + msgParent = aMsg->parent(); + if (msgParent) + { + if (msgParent==folder() && !kmkernel->folderIsDraftOrOutbox(folder())) + return 0; + + idx = msgParent->find(aMsg); + msgParent->getMsg( idx ); + } + + aMsg->setStatusFields(); + if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by + aMsg->removeHeaderField("Content-Type"); // the line above + + + const QString uidHeader = aMsg->headerField( "X-UID" ); + if ( !uidHeader.isEmpty() && stripUid ) + aMsg->removeHeaderField( "X-UID" ); + + msgText = aMsg->asString(); // TODO use asDwString instead + len = msgText.length(); + + // Re-add the uid so that the take can make use of it, in case the + // message is currently in an imap folder + if ( !uidHeader.isEmpty() && stripUid ) + aMsg->setHeaderField( "X-UID", uidHeader ); + + if (len <= 0) + { + kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; + return 0; + } + + // make sure the filename has the correct extension + QString filename = constructValidFileName( aMsg->fileName(), aMsg->status() ); + + QString tmp_file(location() + "/tmp/"); + tmp_file += filename; + + if (!KPIM::kCStringToFile(msgText, tmp_file, false, false, false)) + kmkernel->emergencyExit( i18n("Message could not be added to the folder, possibly disk space is low.") ); + + QFile file(tmp_file); + size = msgText.length(); + + KMFolderOpener openThis(folder(), "maildir"); + rc = openThis.openResult(); + if (rc) + { + kdDebug(5006) << "KMFolderMaildir::addMsg-open: " << rc << " of folder: " << label() << endl; + return rc; + } + + // now move the file to the correct location + QString new_loc(location() + "/cur/"); + new_loc += filename; + if (moveInternal(tmp_file, new_loc, filename, aMsg->status()).isNull()) + { + file.remove(); + return -1; + } + + if (msgParent && idx >= 0) + msgParent->take(idx); + + // just to be sure it does not end up in the index + if ( stripUid ) aMsg->setUID( 0 ); + + if (filename != aMsg->fileName()) + aMsg->setFileName(filename); + + if (aMsg->isUnread() || aMsg->isNew() || folder() == kmkernel->outboxFolder()) + { + if (mUnreadMsgs == -1) + mUnreadMsgs = 1; + else + ++mUnreadMsgs; + if ( !mQuiet ) { + kdDebug( 5006 ) << "FolderStorage::msgStatusChanged" << endl; + emit numUnreadMsgsChanged( folder() ); + }else{ + if ( !mEmitChangedTimer->isActive() ) { +// kdDebug( 5006 )<< "QuietTimer started" << endl; + mEmitChangedTimer->start( 3000 ); + } + mChanged = true; + } + } + ++mTotalMsgs; + mSize = -1; + + if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && + aMsg->readyToShow() ) + aMsg->updateAttachmentState(); + + // store information about the position in the folder file in the message + aMsg->setParent(folder()); + aMsg->setMsgSize(size); + idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums ); + if (aMsg->getMsgSerNum() <= 0) + aMsg->setMsgSerNum(); + else + replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx ); + + // write index entry if desired + if (mAutoCreateIndex) + { + assert(mIndexStream != 0); + clearerr(mIndexStream); + fseek(mIndexStream, 0, SEEK_END); + off_t revert = ftell(mIndexStream); + + int len; + KMMsgBase * mb = &aMsg->toMsgBase(); + const uchar *buffer = mb->asIndexString(len); + fwrite(&len,sizeof(len), 1, mIndexStream); + mb->setIndexOffset( ftell(mIndexStream) ); + mb->setIndexLength( len ); + if(fwrite(buffer, len, 1, mIndexStream) != 1) + kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; + + fflush(mIndexStream); + int error = ferror(mIndexStream); + + if ( mExportsSernums ) + error |= appendToFolderIdsFile( idx ); + + if (error) { + kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; + if (ftell(mIndexStream) > revert) { + kdDebug(5006) << "Undoing changes" << endl; + truncate( QFile::encodeName(indexLocation()), revert ); + } + kmkernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss.")); + // exit(1); // don't ever use exit(), use the above! + + /* This code may not be 100% reliable + bool busy = kmkernel->kbp()->isBusy(); + if (busy) kmkernel->kbp()->idle(); + KMessageBox::sorry(0, + i18n("Unable to add message to folder.\n" + "(No space left on device or insufficient quota?)\n" + "Free space and sufficient quota are required to continue safely.")); + if (busy) kmkernel->kbp()->busy(); + */ + return error; + } + } + + if (index_return) + *index_return = idx; + + emitMsgAddedSignals(idx); + needsCompact = true; + +/* +QFile fileD1( "testdat_xx-kmfoldermaildir-1" ); +if( fileD1.open( IO_WriteOnly ) ) { + QDataStream ds( &fileD1 ); + ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); + fileD1.close(); // If data is 0 we just create a zero length file. +} +*/ + return 0; +} + +KMMessage* KMFolderMaildir::readMsg(int idx) +{ + KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; + KMMessage *msg = new KMMessage(*mi); + msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed + mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed + msg->setComplete( true ); + msg->fromDwString(getDwString(idx)); + return msg; +} + +DwString KMFolderMaildir::getDwString(int idx) +{ + KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; + QString abs_file(location() + "/cur/"); + abs_file += mi->fileName(); + QFileInfo fi( abs_file ); + + if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0) + { + FILE* stream = fopen(QFile::encodeName(abs_file), "r+"); + if (stream) { + size_t msgSize = fi.size(); + char* msgText = new char[ msgSize + 1 ]; + fread(msgText, msgSize, 1, stream); + fclose( stream ); + msgText[msgSize] = '\0'; + size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize ); + DwString str; + // the DwString takes possession of msgText, so we must not delete it + str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize ); + return str; + } + } + kdDebug(5006) << "Could not open file r+ " << abs_file << endl; + return DwString(); +} + + +void KMFolderMaildir::readFileHeaderIntern(const QString& dir, const QString& file, KMMsgStatus status) +{ + // we keep our current directory to restore it later + char path_buffer[PATH_MAX]; + if(!::getcwd(path_buffer, PATH_MAX - 1)) + return; + + ::chdir(QFile::encodeName(dir)); + + // messages in the 'cur' directory are Read by default.. but may + // actually be some other state (but not New) + if (status == KMMsgStatusRead) + { + if (file.find(":2,") == -1) + status = KMMsgStatusUnread; + else if (file.right(5) == ":2,RS") + status |= KMMsgStatusReplied; + } + + // open the file and get a pointer to it + QFile f(file); + if ( f.open( IO_ReadOnly ) == false ) { + kdWarning(5006) << "The file '" << QFile::encodeName(dir) << "/" << file + << "' could not be opened for reading the message. " + "Please check ownership and permissions." + << endl; + return; + } + + char line[MAX_LINE]; + bool atEof = false; + bool inHeader = true; + QCString *lastStr = 0; + + QCString dateStr, fromStr, toStr, subjStr; + QCString xmarkStr, replyToIdStr, msgIdStr, referencesStr; + QCString statusStr, replyToAuxIdStr, uidStr; + QCString contentTypeStr, charset; + + // iterate through this file until done + while (!atEof) + { + // if the end of the file has been reached or if there was an error + if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) ) + atEof = true; + + // are we done with this file? if so, compile our info and store + // it in a KMMsgInfo object + if (atEof || !inHeader) + { + msgIdStr = msgIdStr.stripWhiteSpace(); + if( !msgIdStr.isEmpty() ) { + int rightAngle; + rightAngle = msgIdStr.find( '>' ); + if( rightAngle != -1 ) + msgIdStr.truncate( rightAngle + 1 ); + } + + replyToIdStr = replyToIdStr.stripWhiteSpace(); + if( !replyToIdStr.isEmpty() ) { + int rightAngle; + rightAngle = replyToIdStr.find( '>' ); + if( rightAngle != -1 ) + replyToIdStr.truncate( rightAngle + 1 ); + } + + referencesStr = referencesStr.stripWhiteSpace(); + if( !referencesStr.isEmpty() ) { + int leftAngle, rightAngle; + leftAngle = referencesStr.findRev( '<' ); + if( ( leftAngle != -1 ) + && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) { + // use the last reference, instead of missing In-Reply-To + replyToIdStr = referencesStr.mid( leftAngle ); + } + + // find second last reference + leftAngle = referencesStr.findRev( '<', leftAngle - 1 ); + if( leftAngle != -1 ) + referencesStr = referencesStr.mid( leftAngle ); + rightAngle = referencesStr.findRev( '>' ); + if( rightAngle != -1 ) + referencesStr.truncate( rightAngle + 1 ); + + // Store the second to last reference in the replyToAuxIdStr + // It is a good candidate for threading the message below if the + // message In-Reply-To points to is not kept in this folder, + // but e.g. in an Outbox + replyToAuxIdStr = referencesStr; + rightAngle = referencesStr.find( '>' ); + if( rightAngle != -1 ) + replyToAuxIdStr.truncate( rightAngle + 1 ); + } + + statusStr = statusStr.stripWhiteSpace(); + if (!statusStr.isEmpty()) + { + // only handle those states not determined by the file suffix + if (statusStr[0] == 'S') + status |= KMMsgStatusSent; + else if (statusStr[0] == 'F') + status |= KMMsgStatusForwarded; + else if (statusStr[0] == 'D') + status |= KMMsgStatusDeleted; + else if (statusStr[0] == 'Q') + status |= KMMsgStatusQueued; + else if (statusStr[0] == 'G') + status |= KMMsgStatusFlag; + } + + contentTypeStr = contentTypeStr.stripWhiteSpace(); + charset = ""; + if ( !contentTypeStr.isEmpty() ) + { + int cidx = contentTypeStr.find( "charset=" ); + if ( cidx != -1 ) { + charset = contentTypeStr.mid( cidx + 8 ); + if ( !charset.isEmpty() && ( charset[0] == '"' ) ) { + charset = charset.mid( 1 ); + } + cidx = 0; + while ( (unsigned int) cidx < charset.length() ) { + if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) && + charset[cidx] != '-' && charset[cidx] != '_' ) ) + break; + ++cidx; + } + charset.truncate( cidx ); + // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " << + // charset << " from " << contentTypeStr << endl; + } + } + + KMMsgInfo *mi = new KMMsgInfo(folder()); + mi->init( subjStr.stripWhiteSpace(), + fromStr.stripWhiteSpace(), + toStr.stripWhiteSpace(), + 0, status, + xmarkStr.stripWhiteSpace(), + replyToIdStr, replyToAuxIdStr, msgIdStr, + file.local8Bit(), + KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, + KMMsgMDNStateUnknown, charset, f.size() ); + + dateStr = dateStr.stripWhiteSpace(); + if (!dateStr.isEmpty()) + mi->setDate(dateStr); + if ( !uidStr.isEmpty() ) + mi->setUID( uidStr.toULong() ); + mi->setDirty(false); + mMsgList.append( mi, mExportsSernums ); + + // if this is a New file and is in 'new', we move it to 'cur' + if (status & KMMsgStatusNew) + { + QString newDir(location() + "/new/"); + QString curDir(location() + "/cur/"); + moveInternal(newDir + file, curDir + file, mi); + } + + break; + } + + // Is this a long header line? + if (inHeader && line[0] == '\t' || line[0] == ' ') + { + int i = 0; + while (line[i] == '\t' || line[i] == ' ') + i++; + if (line[i] < ' ' && line[i] > 0) + inHeader = false; + else + if (lastStr) + *lastStr += line + i; + } + else + lastStr = 0; + + if (inHeader && (line[0] == '\n' || line[0] == '\r')) + inHeader = false; + if (!inHeader) + continue; + + if (strncasecmp(line, "Date:", 5) == 0) + { + dateStr = QCString(line+5); + lastStr = &dateStr; + } + else if (strncasecmp(line, "From:", 5) == 0) + { + fromStr = QCString(line+5); + lastStr = &fromStr; + } + else if (strncasecmp(line, "To:", 3) == 0) + { + toStr = QCString(line+3); + lastStr = &toStr; + } + else if (strncasecmp(line, "Subject:", 8) == 0) + { + subjStr = QCString(line+8); + lastStr = &subjStr; + } + else if (strncasecmp(line, "References:", 11) == 0) + { + referencesStr = QCString(line+11); + lastStr = &referencesStr; + } + else if (strncasecmp(line, "Message-Id:", 11) == 0) + { + msgIdStr = QCString(line+11); + lastStr = &msgIdStr; + } + else if (strncasecmp(line, "X-KMail-Mark:", 13) == 0) + { + xmarkStr = QCString(line+13); + } + else if (strncasecmp(line, "X-Status:", 9) == 0) + { + statusStr = QCString(line+9); + } + else if (strncasecmp(line, "In-Reply-To:", 12) == 0) + { + replyToIdStr = QCString(line+12); + lastStr = &replyToIdStr; + } + else if (strncasecmp(line, "X-UID:", 6) == 0) + { + uidStr = QCString(line+6); + lastStr = &uidStr; + } + else if (strncasecmp(line, "Content-Type:", 13) == 0) + { + contentTypeStr = QCString(line+13); + lastStr = &contentTypeStr; + } + + } + + if (status & KMMsgStatusNew || status & KMMsgStatusUnread || + (folder() == kmkernel->outboxFolder())) + { + mUnreadMsgs++; + if (mUnreadMsgs == 0) ++mUnreadMsgs; + } + + ::chdir(path_buffer); +} + +int KMFolderMaildir::createIndexFromContents() +{ + mUnreadMsgs = 0; + + mMsgList.clear(true); + mMsgList.reset(INIT_MSGS); + + mChanged = false; + + // first, we make sure that all the directories are here as they + // should be + QFileInfo dirinfo; + + dirinfo.setFile(location() + "/new"); + if (!dirinfo.exists() || !dirinfo.isDir()) + { + kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl; + return 1; + } + QDir newDir(location() + "/new"); + newDir.setFilter(QDir::Files); + + dirinfo.setFile(location() + "/cur"); + if (!dirinfo.exists() || !dirinfo.isDir()) + { + kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl; + return 1; + } + QDir curDir(location() + "/cur"); + curDir.setFilter(QDir::Files); + + // then, we look for all the 'cur' files + const QFileInfoList *list = curDir.entryInfoList(); + QFileInfoListIterator it(*list); + QFileInfo *fi; + + while ((fi = it.current())) + { + readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead); + ++it; + } + + // then, we look for all the 'new' files + list = newDir.entryInfoList(); + it = *list; + + while ((fi=it.current())) + { + readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew); + ++it; + } + + if ( autoCreateIndex() ) { + emit statusMsg(i18n("Writing index file")); + writeIndex(); + } + else mHeaderOffset = 0; + + correctUnreadMsgsCount(); + + if (kmkernel->outboxFolder() == folder() && count() > 0) + KMessageBox::information(0, i18n("Your outbox contains messages which were " + "most-likely not created by KMail;\nplease remove them from there if you " + "do not want KMail to send them.")); + + needsCompact = true; + + invalidateFolder(); + return 0; +} + +KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus() +{ + QFileInfo new_info(location() + "/new"); + QFileInfo cur_info(location() + "/cur"); + QFileInfo index_info(indexLocation()); + + if (!index_info.exists()) + return KMFolderIndex::IndexMissing; + + // Check whether the directories are more than 5 seconds newer than the index + // file. The 5 seconds are added to reduce the number of false alerts due + // to slightly out of sync clocks of the NFS server and the local machine. + return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) || + (cur_info.lastModified() > index_info.lastModified().addSecs(5))) + ? KMFolderIndex::IndexTooOld + : KMFolderIndex::IndexOk; +} + +//----------------------------------------------------------------------------- +void KMFolderMaildir::removeMsg(int idx, bool) +{ + KMMsgBase* msg = mMsgList[idx]; + if (!msg || !msg->fileName()) return; + + removeFile(msg->fileName()); + + KMFolderIndex::removeMsg(idx); +} + +//----------------------------------------------------------------------------- +KMMessage* KMFolderMaildir::take(int idx) +{ + // first, we do the high-level stuff.. then delete later + KMMessage *msg = KMFolderIndex::take(idx); + + if (!msg || !msg->fileName()) { + return 0; + } + + if ( removeFile(msg->fileName()) ) { + return msg; + } else { + return 0; + } +} + +// static +bool KMFolderMaildir::removeFile( const QString & folderPath, + const QString & filename ) +{ + // we need to look in both 'new' and 'cur' since it's possible to + // delete a message before the folder is compacted. Since the file + // naming and moving is done in ::compact, we can't assume any + // location at this point. + QCString abs_file( QFile::encodeName( folderPath + "/cur/" + filename ) ); + if ( ::unlink( abs_file ) == 0 ) + return true; + + if ( errno == ENOENT ) { // doesn't exist + abs_file = QFile::encodeName( folderPath + "/new/" + filename ); + if ( ::unlink( abs_file ) == 0 ) + return true; + } + + kdDebug(5006) << "Can't delete " << abs_file << " " << perror << endl; + return false; +} + +bool KMFolderMaildir::removeFile( const QString & filename ) +{ + return removeFile( location(), filename ); +} + +#include <sys/types.h> +#include <dirent.h> +static bool removeDirAndContentsRecursively( const QString & path ) +{ + bool success = true; + + QDir d; + d.setPath( path ); + d.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks ); + + const QFileInfoList *list = d.entryInfoList(); + QFileInfoListIterator it( *list ); + QFileInfo *fi; + + while ( (fi = it.current()) != 0 ) { + if( fi->isDir() ) { + if ( fi->fileName() != "." && fi->fileName() != ".." ) + success = success && removeDirAndContentsRecursively( fi->absFilePath() ); + } else { + success = success && d.remove( fi->absFilePath() ); + } + ++it; + } + + if ( success ) { + success = success && d.rmdir( path ); // nuke ourselves, we should be empty now + } + return success; +} + +//----------------------------------------------------------------------------- +int KMFolderMaildir::removeContents() +{ + // NOTE: Don' use KIO::netaccess, it has reentrancy problems and multiple + // mailchecks going on trigger them, when removing dirs + if ( !removeDirAndContentsRecursively( location() + "/new/" ) ) return 1; + if ( !removeDirAndContentsRecursively( location() + "/cur/" ) ) return 1; + if ( !removeDirAndContentsRecursively( location() + "/tmp/" ) ) return 1; + /* The subdirs are removed now. Check if there is anything else in the dir + * and only if not delete the dir itself. The user could have data stored + * that would otherwise be deleted. */ + QDir dir(location()); + if ( dir.count() == 2 ) { // only . and .. + if ( !removeDirAndContentsRecursively( location() ), 0 ) return 1; + } + return 0; +} + +static QRegExp *suffix_regex = 0; +static KStaticDeleter<QRegExp> suffix_regex_sd; + +//----------------------------------------------------------------------------- +// static +QString KMFolderMaildir::constructValidFileName( const QString & filename, + KMMsgStatus status ) +{ + QString aFileName( filename ); + + if (aFileName.isEmpty()) + { + aFileName.sprintf("%ld.%d.", (long)time(0), getpid()); + aFileName += KApplication::randomString(5); + } + + if (!suffix_regex) + suffix_regex_sd.setObject(suffix_regex, new QRegExp(":2,?R?S?$")); + + aFileName.truncate(aFileName.findRev(*suffix_regex)); + + // only add status suffix if the message is neither new nor unread + if (! ((status & KMMsgStatusNew) || (status & KMMsgStatusUnread)) ) + { + QString suffix( ":2," ); + if (status & KMMsgStatusReplied) + suffix += "RS"; + else + suffix += "S"; + aFileName += suffix; + } + + return aFileName; +} + +//----------------------------------------------------------------------------- +QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, KMMsgInfo *mi) +{ + QString filename(mi->fileName()); + QString ret(moveInternal(oldLoc, newLoc, filename, mi->status())); + + if (filename != mi->fileName()) + mi->setFileName(filename); + + return ret; +} + +//----------------------------------------------------------------------------- +QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, KMMsgStatus status) +{ + QString dest(newLoc); + // make sure that our destination filename doesn't already exist + while (QFile::exists(dest)) + { + aFileName = constructValidFileName( QString(), status ); + + QFileInfo fi(dest); + dest = fi.dirPath(true) + "/" + aFileName; + setDirty( true ); + } + + QDir d; + if (d.rename(oldLoc, dest) == false) + return QString::null; + else + return dest; +} + +//----------------------------------------------------------------------------- +void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus, + const KMMsgStatus newStatus, int idx) +{ + // if the status of any message changes, then we need to compact + needsCompact = true; + + KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx); +} + +/*virtual*/ +Q_INT64 KMFolderMaildir::doFolderSize() const +{ + if ( mCurrentlyCheckingFolderSize ) + { + return -1; + } + mCurrentlyCheckingFolderSize = true; + + KFileItemList list; + KFileItem *item = 0; + item = new KFileItem( S_IFDIR, -1, location() + "/cur" ); + list.append( item ); + item = new KFileItem( S_IFDIR, -1, location() + "/new" ); + list.append( item ); + item = new KFileItem( S_IFDIR, -1, location() + "/tmp" ); + list.append( item ); + s_DirSizeJobQueue.append( + qMakePair( QGuardedPtr<const KMFolderMaildir>( this ), list ) ); + + // if there's only one entry in the queue then we can start + // a dirSizeJob right away + if ( s_DirSizeJobQueue.size() == 1 ) + { + //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder " + // << location() << endl; + KDirSize* job = KDirSize::dirSizeJob( list ); + connect( job, SIGNAL( result( KIO::Job* ) ), + this, SLOT( slotDirSizeJobResult( KIO::Job* ) ) ); + } + + return -1; +} + +void KMFolderMaildir::slotDirSizeJobResult( KIO::Job* job ) +{ + mCurrentlyCheckingFolderSize = false; + KDirSize * dirsize = dynamic_cast<KDirSize*>( job ); + if ( dirsize && ! dirsize->error() ) + { + mSize = dirsize->totalSize(); + //kdDebug(5006) << k_funcinfo << "dirSizeJob completed. Folder " + // << location() << " has size " << mSize << endl; + emit folderSizeChanged(); + } + // remove the completed job from the queue + s_DirSizeJobQueue.pop_front(); + + // process the next entry in the queue + while ( s_DirSizeJobQueue.size() > 0 ) + { + DirSizeJobQueueEntry entry = s_DirSizeJobQueue.first(); + // check whether the entry is valid, i.e. whether the folder still exists + if ( entry.first ) + { + // start the next dirSizeJob + //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder " + // << entry.first->location() << endl; + KDirSize* job = KDirSize::dirSizeJob( entry.second ); + connect( job, SIGNAL( result( KIO::Job* ) ), + entry.first, SLOT( slotDirSizeJobResult( KIO::Job* ) ) ); + break; + } + else + { + // remove the invalid entry from the queue + s_DirSizeJobQueue.pop_front(); + } + } +} + +#include "kmfoldermaildir.moc" |