diff options
Diffstat (limited to 'kmail/kmfoldermbox.cpp')
-rw-r--r-- | kmail/kmfoldermbox.cpp | 1278 |
1 files changed, 1278 insertions, 0 deletions
diff --git a/kmail/kmfoldermbox.cpp b/kmail/kmfoldermbox.cpp new file mode 100644 index 000000000..92f466a7c --- /dev/null +++ b/kmail/kmfoldermbox.cpp @@ -0,0 +1,1278 @@ +/* -*- c-basic-offset: 2 -*- + * kmail: KDE mail client + * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org> + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + */ +#include <config.h> +#include <qfileinfo.h> +#include <qregexp.h> + +#include "kmfoldermbox.h" +#include "folderstorage.h" +#include "kmfolder.h" +#include "kmkernel.h" +#include "kmmsgdict.h" +#include "undostack.h" +#include "kcursorsaver.h" +#include "jobscheduler.h" +#include "compactionjob.h" +#include "util.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <knotifyclient.h> +#include <kprocess.h> +#include <kconfig.h> + +#include <ctype.h> +#include <stdio.h> +#include <errno.h> +#include <assert.h> +#include <ctype.h> +#include <unistd.h> + +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include "broadcaststatus.h" +using KPIM::BroadcastStatus; + +#ifndef MAX_LINE +#define MAX_LINE 4096 +#endif +#ifndef INIT_MSGS +#define INIT_MSGS 8 +#endif + +// Regular expression to find the line that seperates messages in a mail +// folder: +#define MSG_SEPERATOR_START "From " +#define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1) +#define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]" + + +//----------------------------------------------------------------------------- +KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name) + : KMFolderIndex(folder, name) +{ + mStream = 0; + mFilesLocked = false; + mReadOnly = false; + mLockType = lock_none; +} + + +//----------------------------------------------------------------------------- +KMFolderMbox::~KMFolderMbox() +{ + if (mOpenCount>0) + close("~kmfoldermbox", true); + if (kmkernel->undoStack()) + kmkernel->undoStack()->folderDestroyed( folder() ); +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::open(const char *owner) +{ + int rc = 0; + + mOpenCount++; + kmkernel->jobScheduler()->notifyOpeningFolder( folder() ); + + if (mOpenCount > 1) return 0; // already open + + assert(!folder()->name().isEmpty()); + + mFilesLocked = false; + mStream = fopen(QFile::encodeName(location()), "r+"); // messages file + if (!mStream) + { + KNotifyClient::event( 0, "warning", + i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno))); + kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl; + mOpenCount = 0; + return errno; + } + + lock(); + + if (!folder()->path().isEmpty()) + { + KMFolderIndex::IndexStatus index_status = indexStatus(); + // test if index file exists and is up-to-date + if (KMFolderIndex::IndexOk != index_status) + { + // only show a warning if the index file exists, otherwise it can be + // silently regenerated + if (KMFolderIndex::IndexTooOld == index_status) { + QString msg = i18n("<qt><p>The index of folder '%2' seems " + "to be out of date. To prevent message " + "corruption the index will be " + "regenerated. As a result deleted " + "messages might reappear and status " + "flags might be lost.</p>" + "<p>Please read the corresponding entry " + "in the <a href=\"%1\">FAQ section of the manual " + "of KMail</a> for " + "information about how to prevent this " + "problem from happening again.</p></qt>") + .arg("help:/kmail/faq.html#faq-index-regeneration") + .arg(name()); + // When KMail is starting up we have to show a non-blocking message + // box so that the initialization can continue. We don't show a + // queued message box when KMail isn't starting up because queued + // message boxes don't have a "Don't ask again" checkbox. + if (kmkernel->startingUp()) + { + KConfigGroup configGroup( KMKernel::config(), "Notification Messages" ); + bool showMessage = + configGroup.readBoolEntry( "showIndexRegenerationMessage", true ); + if (showMessage) + KMessageBox::queuedMessageBox( 0, KMessageBox::Information, + msg, i18n("Index Out of Date"), + KMessageBox::AllowLink ); + } + else + { + KCursorSaver idle(KBusyPtr::idle()); + KMessageBox::information( 0, msg, i18n("Index Out of Date"), + "showIndexRegenerationMessage", + KMessageBox::AllowLink ); + } + } + 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 + if (!readIndex()) + rc = createIndexFromContents(); + } + else + { + mAutoCreateIndex = false; + rc = createIndexFromContents(); + } + + mChanged = false; + + fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); + if (mIndexStream) + fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); + + return rc; +} + +//---------------------------------------------------------------------------- +int KMFolderMbox::canAccess() +{ + assert(!folder()->name().isEmpty()); + + if (access(QFile::encodeName(location()), R_OK | W_OK) != 0) { + kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl; + return 1; + } + return 0; +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::create() +{ + int rc; + int old_umask; + + assert(!folder()->name().isEmpty()); + assert(mOpenCount == 0); + + kdDebug(5006) << "Creating folder " << name() << endl; + if (access(QFile::encodeName(location()), F_OK) == 0) { + kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl; + kdDebug(5006) << "File:: " << endl; + kdDebug(5006) << "Error " << endl; + return EEXIST; + } + + old_umask = umask(077); + mStream = fopen(QFile::encodeName(location()), "w+"); //sven; open RW + umask(old_umask); + + if (!mStream) return errno; + + fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); + + 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(); + if (!rc) lock(); + return rc; +} + + +//----------------------------------------------------------------------------- +void KMFolderMbox::reallyDoClose(const char* owner) +{ + if (mAutoCreateIndex) + { + if (KMFolderIndex::IndexOk != indexStatus()) { + kdDebug(5006) << "Critical error: " << location() << + " has been modified by an external application while KMail was running." << endl; + // exit(1); backed out due to broken nfs + } + + updateIndex(); + writeConfig(); + } + + if (!noContent()) { + if (mStream) unlock(); + mMsgList.clear(true); + + if (mStream) fclose(mStream); + if (mIndexStream) { + fclose(mIndexStream); + updateIndexStreamPtr(true); + } + } + mOpenCount = 0; + mStream = 0; + mIndexStream = 0; + mFilesLocked = false; + mUnreadMsgs = -1; + + mMsgList.reset(INIT_MSGS); +} + +//----------------------------------------------------------------------------- +void KMFolderMbox::sync() +{ + if (mOpenCount > 0) + if (!mStream || fsync(fileno(mStream)) || + !mIndexStream || fsync(fileno(mIndexStream))) { + kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").arg( indexLocation() ).arg(errno ? QString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug."))); + } +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::lock() +{ + int rc; + struct flock fl; + fl.l_type=F_WRLCK; + fl.l_whence=0; + fl.l_start=0; + fl.l_len=0; + fl.l_pid=-1; + QCString cmd_str; + assert(mStream != 0); + mFilesLocked = false; + mReadOnly = false; + + switch( mLockType ) + { + case FCNTL: + rc = fcntl(fileno(mStream), F_SETLKW, &fl); + + if (rc < 0) + { + kdDebug(5006) << "Cannot lock folder `" << location() << "': " + << strerror(errno) << " (" << errno << ")" << endl; + mReadOnly = true; + return errno; + } + + if (mIndexStream) + { + rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); + + if (rc < 0) + { + kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " + << strerror(errno) << " (" << errno << ")" << endl; + rc = errno; + fl.l_type = F_UNLCK; + /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl); + mReadOnly = true; + return rc; + } + } + break; + + case procmail_lockfile: + cmd_str = "lockfile -l20 -r5 "; + if (!mProcmailLockFileName.isEmpty()) + cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); + else + cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); + + rc = system( cmd_str.data() ); + if( rc != 0 ) + { + kdDebug(5006) << "Cannot lock folder `" << location() << "': " + << strerror(rc) << " (" << rc << ")" << endl; + mReadOnly = true; + return rc; + } + if( mIndexStream ) + { + cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); + rc = system( cmd_str.data() ); + if( rc != 0 ) + { + kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " + << strerror(rc) << " (" << rc << ")" << endl; + mReadOnly = true; + return rc; + } + } + break; + + case mutt_dotlock: + cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(location())); + rc = system( cmd_str.data() ); + if( rc != 0 ) + { + kdDebug(5006) << "Cannot lock folder `" << location() << "': " + << strerror(rc) << " (" << rc << ")" << endl; + mReadOnly = true; + return rc; + } + if( mIndexStream ) + { + cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(indexLocation())); + rc = system( cmd_str.data() ); + if( rc != 0 ) + { + kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " + << strerror(rc) << " (" << rc << ")" << endl; + mReadOnly = true; + return rc; + } + } + break; + + case mutt_dotlock_privileged: + cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(location())); + rc = system( cmd_str.data() ); + if( rc != 0 ) + { + kdDebug(5006) << "Cannot lock folder `" << location() << "': " + << strerror(rc) << " (" << rc << ")" << endl; + mReadOnly = true; + return rc; + } + if( mIndexStream ) + { + cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(indexLocation())); + rc = system( cmd_str.data() ); + if( rc != 0 ) + { + kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " + << strerror(rc) << " (" << rc << ")" << endl; + mReadOnly = true; + return rc; + } + } + break; + + case lock_none: + default: + break; + } + + + mFilesLocked = true; + return 0; +} + +//------------------------------------------------------------- +FolderJob* +KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt, + KMFolder *folder, QString, const AttachmentStrategy* ) const +{ + MboxJob *job = new MboxJob( msg, jt, folder ); + job->setParent( this ); + return job; +} + +//------------------------------------------------------------- +FolderJob* +KMFolderMbox::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets, + FolderJob::JobType jt, KMFolder *folder ) const +{ + MboxJob *job = new MboxJob( msgList, sets, jt, folder ); + job->setParent( this ); + return job; +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::unlock() +{ + int rc; + struct flock fl; + fl.l_type=F_UNLCK; + fl.l_whence=0; + fl.l_start=0; + fl.l_len=0; + QCString cmd_str; + + assert(mStream != 0); + mFilesLocked = false; + + switch( mLockType ) + { + case FCNTL: + if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl); + fcntl(fileno(mStream), F_SETLK, &fl); + rc = errno; + break; + + case procmail_lockfile: + cmd_str = "rm -f "; + if (!mProcmailLockFileName.isEmpty()) + cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); + else + cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); + + rc = system( cmd_str.data() ); + if( mIndexStream ) + { + cmd_str = "rm -f " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); + rc = system( cmd_str.data() ); + } + break; + + case mutt_dotlock: + cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(location())); + rc = system( cmd_str.data() ); + if( mIndexStream ) + { + cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(indexLocation())); + rc = system( cmd_str.data() ); + } + break; + + case mutt_dotlock_privileged: + cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(location())); + rc = system( cmd_str.data() ); + if( mIndexStream ) + { + cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(indexLocation())); + rc = system( cmd_str.data() ); + } + break; + + case lock_none: + default: + rc = 0; + break; + } + + return rc; +} + + +//----------------------------------------------------------------------------- +KMFolderIndex::IndexStatus KMFolderMbox::indexStatus() +{ + QFileInfo contInfo(location()); + QFileInfo indInfo(indexLocation()); + + if (!contInfo.exists()) return KMFolderIndex::IndexOk; + if (!indInfo.exists()) return KMFolderIndex::IndexMissing; + + // Check whether the mbox file is 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 ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) ) + ? KMFolderIndex::IndexTooOld + : KMFolderIndex::IndexOk; +} + + +//----------------------------------------------------------------------------- +int KMFolderMbox::createIndexFromContents() +{ + char line[MAX_LINE]; + char status[8], xstatus[8]; + QCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0; + QCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr; + QCString sizeServerStr, uidStr; + QCString contentTypeStr, charset; + bool atEof = false; + bool inHeader = true; + KMMsgInfo* mi; + QString msgStr; + QRegExp regexp(MSG_SEPERATOR_REGEX); + int i, num, numStatus; + short needStatus; + + assert(mStream != 0); + rewind(mStream); + + mMsgList.clear(); + + num = -1; + numStatus= 11; + off_t offs = 0; + size_t size = 0; + dateStr = ""; + fromStr = ""; + toStr = ""; + subjStr = ""; + *status = '\0'; + *xstatus = '\0'; + xmarkStr = ""; + replyToIdStr = ""; + replyToAuxIdStr = ""; + referencesStr = ""; + msgIdStr = ""; + needStatus = 3; + size_t sizeServer = 0; + ulong uid = 0; + + + while (!atEof) + { + off_t pos = ftell(mStream); + if (!fgets(line, MAX_LINE, mStream)) atEof = true; + + if (atEof || + (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 && + regexp.search(line) >= 0)) + { + size = pos - offs; + pos = ftell(mStream); + + if (num >= 0) + { + if (numStatus <= 0) + { + msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num); + emit statusMsg(msgStr); + numStatus = 10; + } + + if (size > 0) + { + 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 ); + } + + 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; + } + } + + mi = new KMMsgInfo(folder()); + mi->init( subjStr.stripWhiteSpace(), + fromStr.stripWhiteSpace(), + toStr.stripWhiteSpace(), + 0, KMMsgStatusNew, + xmarkStr.stripWhiteSpace(), + replyToIdStr, replyToAuxIdStr, msgIdStr, + KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, + KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid ); + mi->setStatus(status, xstatus); + mi->setDate( dateStr.stripWhiteSpace() ); + mi->setDirty(false); + mMsgList.append(mi, mExportsSernums ); + + *status = '\0'; + *xstatus = '\0'; + needStatus = 3; + xmarkStr = ""; + replyToIdStr = ""; + replyToAuxIdStr = ""; + referencesStr = ""; + msgIdStr = ""; + dateStr = ""; + fromStr = ""; + subjStr = ""; + sizeServer = 0; + uid = 0; + } + else num--,numStatus++; + } + + offs = ftell(mStream); + num++; + numStatus--; + inHeader = true; + continue; + } + // Is this a long header line? + if (inHeader && (line[0]=='\t' || line[0]==' ')) + { + 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; + + /* -sanders Make all messages read when auto-recreating index */ + /* Reverted, as it breaks reading the sent mail status, for example. + -till */ + if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0) + { + for(i=0; i<4 && line[i+8] > ' '; i++) + status[i] = line[i+8]; + status[i] = '\0'; + needStatus &= ~1; + } + else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0) + { + for(i=0; i<4 && line[i+10] > ' '; i++) + xstatus[i] = line[i+10]; + xstatus[i] = '\0'; + needStatus &= ~2; + } + else if (strncasecmp(line,"X-KMail-Mark:",13)==0) + xmarkStr = QCString(line+13); + else if (strncasecmp(line,"In-Reply-To:",12)==0) { + replyToIdStr = QCString(line+12); + lastStr = &replyToIdStr; + } + 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,"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,"X-Length:",9)==0) + { + sizeServerStr = QCString(line+9); + sizeServer = sizeServerStr.toULong(); + lastStr = &sizeServerStr; + } + else if (strncasecmp(line,"X-UID:",6)==0) + { + uidStr = QCString(line+6); + uid = uidStr.toULong(); + lastStr = &uidStr; + } + else if (strncasecmp(line, "Content-Type:", 13) == 0) + { + contentTypeStr = QCString(line+13); + lastStr = &contentTypeStr; + } + } + + if (mAutoCreateIndex) + { + emit statusMsg(i18n("Writing index file")); + writeIndex(); + } + else mHeaderOffset = 0; + + correctUnreadMsgsCount(); + + if (kmkernel->outboxFolder() == folder() && count() > 0) + KMessageBox::queuedMessageBox(0, KMessageBox::Information, + 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.")); + + invalidateFolder(); + return 0; +} + + +//----------------------------------------------------------------------------- +KMMessage* KMFolderMbox::readMsg(int idx) +{ + KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; + + assert(mi!=0 && !mi->isMessage()); + assert(mStream != 0); + + 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->fromDwString(getDwString(idx)); + return msg; +} + + +#define STRDIM(x) (sizeof(x)/sizeof(*x)-1) +// performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion +static size_t unescapeFrom( char* str, size_t strLen ) { + if ( !str ) + return 0; + if ( strLen <= STRDIM(">From ") ) + return strLen; + + // yes, *d++ = *s++ is a no-op as long as d == s (until after the + // first >From_), but writes are cheap compared to reads and the + // data is already in the cache from the read, so special-casing + // might even be slower... + const char * s = str; + char * d = str; + const char * const e = str + strLen - STRDIM(">From "); + + while ( s < e ) { + if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end! + *d++ = *s++; // == '\n' + *d++ = *s++; // == '>' + while ( s < e && *s == '>' ) + *d++ = *s++; + if ( qstrncmp( s, "From ", STRDIM("From ") ) == 0 ) + --d; + } + *d++ = *s++; // yes, s might be e here, but e is not the end :-) + } + // copy the rest: + while ( s < str + strLen ) + *d++ = *s++; + if ( d < s ) // only NUL-terminate if it's shorter + *d = 0; + + return d - str; +} + +//static +QByteArray KMFolderMbox::escapeFrom( const DwString & str ) { + const unsigned int strLen = str.length(); + if ( strLen <= STRDIM("From ") ) + return KMail::Util::ByteArray( str ); + // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6 + QByteArray result( int( strLen + 5 ) / 6 * 7 + 1 ); + + const char * s = str.data(); + const char * const e = s + strLen - STRDIM("From "); + char * d = result.data(); + + bool onlyAnglesAfterLF = false; // dont' match ^From_ + while ( s < e ) { + switch ( *s ) { + case '\n': + onlyAnglesAfterLF = true; + break; + case '>': + break; + case 'F': + if ( onlyAnglesAfterLF && qstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 ) + *d++ = '>'; + // fall through + default: + onlyAnglesAfterLF = false; + break; + } + *d++ = *s++; + } + while ( s < str.data() + strLen ) + *d++ = *s++; + + result.truncate( d - result.data() ); + return result; +} + +#undef STRDIM + +//----------------------------------------------------------------------------- +DwString KMFolderMbox::getDwString(int idx) +{ + KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; + + assert(mi!=0); + assert(mStream != 0); + + size_t msgSize = mi->msgSize(); + char* msgText = new char[ msgSize + 1 ]; + + fseek(mStream, mi->folderOffset(), SEEK_SET); + fread(msgText, msgSize, 1, mStream); + msgText[msgSize] = '\0'; + + size_t newMsgSize = unescapeFrom( msgText, msgSize ); + newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize ); + + DwString msgStr; + // the DwString takes possession of msgText, so we must not delete msgText + msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize ); + return msgStr; +} + + +//----------------------------------------------------------------------------- +int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret ) +{ + if (!canAddMsgNow(aMsg, aIndex_ret)) return 0; + QByteArray msgText; + char endStr[3]; + int idx = -1, rc; + KMFolder* msgParent; + bool editing = false; + int growth = 0; + + KMFolderOpener openThis(folder(), "mboxaddMsg"); + rc = openThis.openResult(); + if (rc) + { + kdDebug(5006) << "KMFolderMbox::addMsg-open: " << rc << " of folder: " << label() << endl; + return rc; + } + + // take message out of the folder it is currently in, if any + msgParent = aMsg->parent(); + if (msgParent) + { + if ( msgParent== folder() ) + { + if (kmkernel->folderIsDraftOrOutbox( folder() )) + //special case for Edit message. + { + kdDebug(5006) << "Editing message in outbox or drafts" << endl; + editing = true; + } + else + return 0; + } + + idx = msgParent->find(aMsg); + msgParent->getMsg( idx ); + } + + if (folderType() != KMFolderTypeImap) + { +/* +QFile fileD0( "testdat_xx-kmfoldermbox-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. +} +*/ + aMsg->setStatusFields(); +/* +QFile fileD1( "testdat_xx-kmfoldermbox-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. +} +*/ + if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by + aMsg->removeHeaderField("Content-Type"); // the line above + } + msgText = escapeFrom( aMsg->asDwString() ); + size_t len = msgText.size(); + + assert(mStream != 0); + clearerr(mStream); + if (len <= 0) + { + kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; + return 0; + } + + // Make sure the file is large enough to check for an end + // character + fseek(mStream, 0, SEEK_END); + off_t revert = ftell(mStream); + if (ftell(mStream) >= 2) { + // write message to folder file + fseek(mStream, -2, SEEK_END); + fread(endStr, 1, 2, mStream); // ensure separating empty line + if (ftell(mStream) > 0 && endStr[0]!='\n') { + ++growth; + if (endStr[1]!='\n') { + //printf ("****endStr[1]=%c\n", endStr[1]); + fwrite("\n\n", 1, 2, mStream); + ++growth; + } + else fwrite("\n", 1, 1, mStream); + } + } + fseek(mStream,0,SEEK_END); // this is needed on solaris and others + int error = ferror(mStream); + if (error) + return error; + + QCString messageSeparator( aMsg->mboxMessageSeparator() ); + fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream ); + off_t offs = ftell(mStream); + fwrite(msgText.data(), len, 1, mStream); + if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream); + fflush(mStream); + size_t size = ftell(mStream) - offs; + + error = ferror(mStream); + if (error) { + kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl; + if (ftell(mStream) > revert) { + kdDebug(5006) << "Undoing changes" << endl; + truncate( QFile::encodeName(location()), revert ); + } + kmkernel->emergencyExit( i18n("Could not add message to folder: ") + QString::fromLocal8Bit(strerror(errno))); + + /* This code is not 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(); + kmkernel->kbp()->idle(); + */ + return error; + } + + if (msgParent) { + if (idx >= 0) msgParent->take(idx); + } +// if (mAccount) aMsg->removeHeaderField("X-UID"); + + if (aMsg->isUnread() || aMsg->isNew() || + (folder() == kmkernel->outboxFolder())) { + if (mUnreadMsgs == -1) mUnreadMsgs = 1; + else ++mUnreadMsgs; + if ( !mQuiet ) + emit numUnreadMsgsChanged( folder() ); + } + ++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->setFolderOffset(offs); + aMsg->setMsgSize(size); + idx = mMsgList.append(&aMsg->toMsgBase(), mExportsSernums ); + if ( aMsg->getMsgSerNum() <= 0 ) + aMsg->setMsgSerNum(); + else + replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx ); + + // change the length of the previous message to encompass white space added + if ((idx > 0) && (growth > 0)) { + // don't grow if a deleted message claims space at the end of the file + if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() ) + mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth ); + } + + // write index entry if desired + if (mAutoCreateIndex) + { + assert(mIndexStream != 0); + clearerr(mIndexStream); + fseek(mIndexStream, 0, SEEK_END); + revert = ftell(mIndexStream); + + KMMsgBase * mb = &aMsg->toMsgBase(); + int len; + 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); + error = ferror(mIndexStream); + + if ( mExportsSernums ) + error |= appendToFolderIdsFile( idx ); + + if (error) { + kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; + if (ftell(mIndexStream) > revert) { + kdWarning(5006) << "Undoing changes" << endl; + truncate( QFile::encodeName(indexLocation()), revert ); + } + if ( errno ) + kmkernel->emergencyExit( i18n("Could not add message to folder:") + QString::fromLocal8Bit(strerror(errno))); + else + kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") ); + + /* 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 (aIndex_ret) *aIndex_ret = idx; + emitMsgAddedSignals(idx); + + // All streams have been flushed without errors if we arrive here + // Return success! + // (Don't return status of stream, it may have been closed already.) + return 0; +} + +int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done ) +{ + int rc = 0; + QCString mtext; + unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() : + QMIN( mMsgList.count(), startIndex + nbMessages ); + //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl; + for(unsigned int idx = startIndex; idx < stopIndex; ++idx) { + KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx); + size_t msize = mi->msgSize(); + if (mtext.size() < msize + 2) + mtext.resize(msize+2); + off_t folder_offset = mi->folderOffset(); + + //now we need to find the separator! grr... + for(off_t i = folder_offset-25; true; i -= 20) { + off_t chunk_offset = i <= 0 ? 0 : i; + if(fseek(mStream, chunk_offset, SEEK_SET) == -1) { + rc = errno; + break; + } + if (mtext.size() < 20) + mtext.resize(20); + fread(mtext.data(), 20, 1, mStream); + if(i <= 0) { //woops we've reached the top of the file, last try.. + if ( mtext.contains( "from ", false ) ) { + if (mtext.size() < (size_t)folder_offset) + mtext.resize(folder_offset); + if(fseek(mStream, chunk_offset, SEEK_SET) == -1 || + !fread(mtext.data(), folder_offset, 1, mStream) || + !fwrite(mtext.data(), folder_offset, 1, tmpfile)) { + rc = errno; + break; + } + offs += folder_offset; + } else { + rc = 666; + } + break; + } else { + int last_crlf = -1; + for(int i2 = 0; i2 < 20; i2++) { + if(*(mtext.data()+i2) == '\n') + last_crlf = i2; + } + if(last_crlf != -1) { + int size = folder_offset - (i + last_crlf+1); + if ((int)mtext.size() < size) + mtext.resize(size); + if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 || + !fread(mtext.data(), size, 1, mStream) || + !fwrite(mtext.data(), size, 1, tmpfile)) { + rc = errno; + break; + } + offs += size; + break; + } + } + } + if (rc) + break; + + //now actually write the message + if(fseek(mStream, folder_offset, SEEK_SET) == -1 || + !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) { + rc = errno; + break; + } + mi->setFolderOffset(offs); + offs += msize; + } + done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors + return rc; +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::compact( bool silent ) +{ + // This is called only when the user explicitely requests compaction, + // so we don't check needsCompact. + + KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ ); + int rc = job->executeNow( silent ); + // Note that job autodeletes itself. + + // If this is the current folder, the changed signal will ultimately call + // KMHeaders::setFolderInfoStatus which will override the message, so save/restore it + QString statusMsg = BroadcastStatus::instance()->statusMsg(); + emit changed(); + BroadcastStatus::instance()->setStatusMsg( statusMsg ); + return rc; +} + + +//----------------------------------------------------------------------------- +void KMFolderMbox::setLockType( LockType ltype ) +{ + mLockType = ltype; +} + +//----------------------------------------------------------------------------- +void KMFolderMbox::setProcmailLockFileName( const QString &fname ) +{ + mProcmailLockFileName = fname; +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::removeContents() +{ + int rc = 0; + rc = unlink(QFile::encodeName(location())); + return rc; +} + +//----------------------------------------------------------------------------- +int KMFolderMbox::expungeContents() +{ + int rc = 0; + if (truncate(QFile::encodeName(location()), 0)) + rc = errno; + return rc; +} + +//----------------------------------------------------------------------------- +/*virtual*/ +Q_INT64 KMFolderMbox::doFolderSize() const +{ + QFileInfo info( location() ); + return (Q_INT64)(info.size()); +} + +//----------------------------------------------------------------------------- +#include "kmfoldermbox.moc" |