You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1146 lines
34 KiB

// -*- mode: C++; c-file-style: "gnu" -*-
// kmfoldermaildir.cpp
// Author: Kurt Granroth <granroth@kde.org>
#include <QDir>
#include <QRegExp>
#include <QByteArray>
#include <QFileInfo>
#include <kpimutils/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 <kio/directorysizejob.h>
#include <kdebug.h>
#include <kde_file.h>
#include <kfileitem.h>
#include <kglobal.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <krandom.h>
#include <QDateTime>
#include <QPointer>
#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 <fcntl.h>
#ifndef MAX_LINE
#define MAX_LINE 4096
#endif
#ifndef INIT_MSGS
#define INIT_MSGS 8
#endif
using KPIMUtils::removeDirAndContentsRecursively;
// A separator for "uniq:info" (see the original maildir specification
// at http://cr.yp.to/proto/maildir.html.
// Windows uses '!' charater instead as ':' is not supported by the OS.
// TODO make it configurable - jstaniek
// TODO check what the choice for Thunderbird 3 - jstaniek
#ifdef Q_WS_WIN
#define KMAIL_MAILDIR_FNAME_SEPARATOR "!"
#else
#define KMAIL_MAILDIR_FNAME_SEPARATOR ":"
#endif
//-----------------------------------------------------------------------------
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() );
}
}
//-----------------------------------------------------------------------------
bool KMFolderMaildir::canAccess() const
{
assert(!folder()->name().isEmpty());
QString sBadFolderName;
QStringList files;
files << "" << "/new" << "/cur" << "/tmp";
foreach( const QString& fname, files ) {
QFileInfo finfo( location() + fname );
if ( !finfo.isDir() || !finfo.isReadable() || !finfo.isWritable() ) {
sBadFolderName = location() + fname;
break;
}
}
if ( sBadFolderName.isEmpty() )
return true;
KCursorSaver idle(KBusyPtr::idle());
if ( !QFile::exists(sBadFolderName) )
KMessageBox::sorry(0, i18n("Error opening %1; this folder is missing.",
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.",
sBadFolderName));
return false;
}
//-----------------------------------------------------------------------------
int KMFolderMaildir::open( const char * )
{
mOpenCount++;
kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
if (mOpenCount > 1) return 0; // already open
assert(!folder()->name().isEmpty());
if ( !canAccess() )
return 1;
int rc = 0;
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.", objectName());
emit statusMsg(str);
} else {
mIndexStream = KDE_fopen(QFile::encodeName(indexLocation()), "r+"); // index file
if ( mIndexStream ) {
fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
updateIndexStreamPtr();
}
}
if (!mIndexStream)
rc = createIndexFromContents();
else
rc = readIndex() ? 0 : 1;
}
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 ( KDE_mkdir( QFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
kDebug(5006) << "Could not create folder" << folderPath;
return errno;
}
if ( KDE_mkdir( QFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
kDebug(5006) << "Could not create folder" << folderPath << "/new";
return errno;
}
if ( KDE_mkdir( QFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
kDebug(5006) << "Could not create folder" << folderPath << "/cur";
return errno;
}
if ( KDE_mkdir( QFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) {
kDebug(5006) << "Could not create folder" << folderPath << "/tmp";
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 = KDE_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::close( const char *, bool aForced )
{
if (mOpenCount <= 0) return;
if (mOpenCount > 0) mOpenCount--;
if (mOpenCount > 0 && !aForced) return;
#if 0 // removed hack that prevented closing system folders (see kmail-devel discussion about mail expiring)
if ( (folder() != kmkernel->inboxFolder())
&& folder()->isSystemFolder() && !aForced)
{
mOpenCount = 1;
return;
}
#endif
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 );
//kDebug(5006) <<"KMFolderMaildir: compacting from" << startIndex <<" to" << stopIndex;
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->messageStatus() );
// 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, const QString&, const AttachmentStrategy* ) const
{
MaildirJob *job = new MaildirJob( msg, jt, folder );
job->setParentFolder( this );
return job;
}
//-------------------------------------------------------------
FolderJob*
KMFolderMaildir::doCreateJob( QList<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( QIODevice::WriteOnly ) ) {
QDataStream ds( &fileD0 );
ds.writeRawData( aMsg->asString(), aMsg->asString().length() );
fileD0.close(); // If data is 0 we just create a zero length file.
}
*/
KMFolder* msgParent;
int idx(-1);
// 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" );
const QByteArray msgText = aMsg->asString();
// 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 ( msgText.length() <= 0 ) {
kDebug(5006) <<"Message added to folder `" << objectName() <<"' contains no data. Ignoring it.";
return 0;
}
// make sure the filename has the correct extension
QString filename = constructValidFileName( aMsg->fileName(), aMsg->messageStatus() );
QString tmp_file(location() + "/tmp/");
tmp_file += filename;
if ( ! KPIMUtils::kByteArrayToFile( 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);
KMFolderOpener openThis(folder(), "maildir");
if (openThis.openResult())
{
kDebug(5006) <<"KMFolderMaildir::addMsg-open:" << openThis.openResult() <<" of folder:" << label();
return openThis.openResult();
}
// now move the file to the correct location
QString new_loc(location() + "/cur/");
new_loc += filename;
if (moveInternal(tmp_file, new_loc, filename, aMsg->messageStatus()).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->status().isUnread() || aMsg->status().isNew() ||
folder() == kmkernel->outboxFolder())
{
if (mUnreadMsgs == -1)
mUnreadMsgs = 1;
else
++mUnreadMsgs;
if ( !mQuiet ) {
kDebug( 5006 ) <<"FolderStorage::msgStatusChanged";
emit numUnreadMsgsChanged( folder() );
}else{
if ( !mEmitChangedTimer->isActive() ) {
// kDebug( 5006 )<<"QuietTimer started";
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( msgText.length() );
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);
KDE_fseek(mIndexStream, 0, SEEK_END);
off_t revert = KDE_ftell(mIndexStream);
int len;
KMMsgBase * mb = &aMsg->toMsgBase();
const uchar *buffer = mb->asIndexString(len);
fwrite(&len,sizeof(len), 1, mIndexStream);
mb->setIndexOffset( KDE_ftell(mIndexStream) );
mb->setIndexLength( len );
if(fwrite(buffer, len, 1, mIndexStream) != 1)
kDebug(5006) <<"Whoa!";
fflush(mIndexStream);
int error = ferror(mIndexStream);
if ( mExportsSernums )
error |= appendToFolderIdsFile( idx );
if ( error ) {
kDebug(5006) << "Error: Could not add message to folder (No space left on device?)";
if ( KDE_ftell( mIndexStream ) > revert ) {
kDebug(5006) << "Undoing changes";
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( QIODevice::WriteOnly ) ) {
QDataStream ds( &fileD1 );
ds.writeRawData( 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); // note that mi is deleted by the line below
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 = KDE_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;
}
}
kDebug(5006) <<"Could not open file r+" << abs_file;
return DwString();
}
void KMFolderMaildir::readFileHeaderIntern( const QString& dir,
const QString& file,
MessageStatus& 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.isRead() ) {
if ( !file.contains(KMAIL_MAILDIR_FNAME_SEPARATOR "2,") ) {
status.setUnread();
} else if ( file.right(5) == KMAIL_MAILDIR_FNAME_SEPARATOR "2,RS" ) {
status.setReplied();
}
}
// open the file and get a pointer to it
QFile f( file );
if ( f.open( QIODevice::ReadOnly ) == false ) {
kWarning(5006) <<"The file '" << QFile::encodeName(dir) <<"/" << file
<< "' could not be opened for reading the message."
<< "Please check ownership and permissions.";
return;
}
char line[MAX_LINE];
bool atEof = false;
bool inHeader = true;
QByteArray *lastStr = 0;
QByteArray dateStr, fromStr, toStr, subjStr;
QByteArray xmarkStr, replyToIdStr, msgIdStr, referencesStr;
QByteArray statusStr, replyToAuxIdStr, uidStr;
QByteArray 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.trimmed();
if( !msgIdStr.isEmpty() ) {
int rightAngle;
rightAngle = msgIdStr.indexOf( '>' );
if( rightAngle != -1 )
msgIdStr.truncate( rightAngle + 1 );
}
replyToIdStr = replyToIdStr.trimmed();
if( !replyToIdStr.isEmpty() ) {
int rightAngle;
rightAngle = replyToIdStr.indexOf( '>' );
if( rightAngle != -1 )
replyToIdStr.truncate( rightAngle + 1 );
}
referencesStr = referencesStr.trimmed();
if( !referencesStr.isEmpty() ) {
int leftAngle, rightAngle;
leftAngle = referencesStr.lastIndexOf( '<' );
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.lastIndexOf( '<', leftAngle - 1 );
if( leftAngle != -1 )
referencesStr = referencesStr.mid( leftAngle );
rightAngle = referencesStr.lastIndexOf( '>' );
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.indexOf( '>' );
if( rightAngle != -1 )
replyToAuxIdStr.truncate( rightAngle + 1 );
}
statusStr = statusStr.trimmed();
if (!statusStr.isEmpty())
{
// only handle those states not determined by the file suffix
if (statusStr[0] == 'S')
status.setSent();
else if (statusStr[0] == 'F')
status.setForwarded();
else if (statusStr[0] == 'D')
status.setDeleted();
else if (statusStr[0] == 'Q')
status.setQueued();
else if (statusStr[0] == 'G')
status.setImportant();
}
contentTypeStr = contentTypeStr.trimmed();
charset = "";
if ( !contentTypeStr.isEmpty() ) {
int cidx = contentTypeStr.indexOf( "charset=" );
if ( cidx != -1 ) {
charset = contentTypeStr.mid( cidx + 8 );
if ( charset[0] == '"' ) {
charset = charset.mid( 1 );
}
cidx = 0;
while ( cidx < charset.length() ) {
if ( charset[cidx] == '"' ||
( !isalnum(charset[cidx] ) &&
charset[cidx] != '-' && charset[cidx] != '_' ) ) {
break;
}
++cidx;
}
charset.truncate( cidx );
}
}
KMMsgInfo *mi = new KMMsgInfo(folder());
mi->init( subjStr.trimmed(),
fromStr.trimmed(),
toStr.trimmed(),
0, status,
xmarkStr.trimmed(),
replyToIdStr, replyToAuxIdStr, msgIdStr,
file.toLocal8Bit(),
KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
KMMsgMDNStateUnknown, charset, f.size() );
dateStr = dateStr.trimmed();
if (!dateStr.isEmpty())
mi->setDate(dateStr.constData());
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.isNew() )
{
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 = QByteArray( line + 5 );
lastStr = &dateStr;
} else if ( strncasecmp( line, "From:", 5 ) == 0 ) {
fromStr = QByteArray( line + 5 );
lastStr = &fromStr;
} else if ( strncasecmp( line, "To:", 3 ) == 0 ) {
toStr = QByteArray( line + 3 );
lastStr = &toStr;
} else if ( strncasecmp( line, "Subject:", 8 ) == 0 ) {
subjStr = QByteArray( line + 8 );
lastStr = &subjStr;
} else if ( strncasecmp( line, "References:", 11 ) == 0 ) {
referencesStr = QByteArray( line + 11 );
lastStr = &referencesStr;
} else if ( strncasecmp( line, "Message-Id:", 11 ) == 0 ) {
msgIdStr = QByteArray( line + 11 );
lastStr = &msgIdStr;
} else if ( strncasecmp( line, "X-KMail-Mark:", 13 ) == 0 ) {
xmarkStr = QByteArray( line + 13 );
} else if ( strncasecmp( line, "X-Status:", 9 ) == 0 ) {
statusStr = QByteArray( line + 9 );
} else if ( strncasecmp( line, "In-Reply-To:", 12 ) == 0 ) {
replyToIdStr = QByteArray( line + 12 );
lastStr = &replyToIdStr;
} else if ( strncasecmp( line, "X-UID:", 6 ) == 0 ) {
uidStr = QByteArray( line + 6 );
lastStr = &uidStr;
} else if ( strncasecmp( line, "Content-Type:", 13 ) == 0) {
contentTypeStr = QByteArray( line + 13 );
lastStr = &contentTypeStr;
}
}
if ( status.isNew() || status.isUnread() ||
(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())
{
kDebug(5006) <<"Directory" << location() <<"/new doesn't exist or is a file";
return 1;
}
QDir newDir(location() + "/new");
newDir.setFilter(QDir::Files);
dirinfo.setFile(location() + "/cur");
if (!dirinfo.exists() || !dirinfo.isDir())
{
kDebug(5006) <<"Directory" << location() <<"/cur doesn't exist or is a file";
return 1;
}
QDir curDir(location() + "/cur");
curDir.setFilter(QDir::Files);
// then, we look for all the 'cur' files
QFileInfoList list = curDir.entryInfoList();
QFileInfo fi;
Q_FOREACH( fi, list )
{
MessageStatus st = MessageStatus::statusRead();
readFileHeaderIntern( curDir.path(), fi.fileName(), st );
}
// then, we look for all the 'new' files
list = newDir.entryInfoList();
Q_FOREACH( fi, list )
{
MessageStatus st = MessageStatus::statusNew();
readFileHeaderIntern( newDir.path(), fi.fileName(), st );
}
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().isNull()) 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().isNull()) {
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.
QByteArray 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;
}
kDebug(5006) <<"Can't delete" << abs_file << perror;
return false;
}
bool KMFolderMaildir::removeFile( const QString & filename )
{
return removeFile( location(), filename );
}
//-----------------------------------------------------------------------------
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;
}
K_GLOBAL_STATIC_WITH_ARGS(QRegExp, s_suffixRegExp, (KMAIL_MAILDIR_FNAME_SEPARATOR "2,?R?S?$"))
//-----------------------------------------------------------------------------
// static
QString KMFolderMaildir::constructValidFileName( const QString & filename,
const MessageStatus & status )
{
QString aFileName( filename );
if (aFileName.isEmpty())
{
aFileName.sprintf("%ld.%d.", (long)time(0), getpid());
aFileName += KRandom::randomString(5);
}
int pos = aFileName.lastIndexOf( *s_suffixRegExp );
if ( pos >= 0 )
aFileName.truncate( pos );
// only add status suffix if the message is neither new nor unread
if (! ( status.isNew() || status.isUnread() ) )
{
QString suffix( KMAIL_MAILDIR_FNAME_SEPARATOR "2," );
if ( status.isReplied() )
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->messageStatus()));
if (filename != mi->fileName())
mi->setFileName(filename);
return ret;
}
//-----------------------------------------------------------------------------
QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, const MessageStatus& 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.absolutePath() + '/' + aFileName;
setDirty( true );
}
QDir d;
if (d.rename(oldLoc, dest) == false)
return QString();
else
return dest;
}
//-----------------------------------------------------------------------------
void KMFolderMaildir::msgStatusChanged( const MessageStatus& oldStatus,
const MessageStatus& newStatus,
int idx )
{
// if the status of any message changes, then we need to compact
needsCompact = true;
KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx);
}
// define the global static s_DirSizeJobQueue used below
typedef QPair<QPointer<KMFolderMaildir>,KFileItemList> DirSizeJobQueueEntry;
typedef QList<DirSizeJobQueueEntry> DirSizeJobQueue;
K_GLOBAL_STATIC( DirSizeJobQueue, s_DirSizeJobQueue )
/*virtual*/
qint64 KMFolderMaildir::doFolderSize() const
{
if ( mCurrentlyCheckingFolderSize )
{
return -1;
}
mCurrentlyCheckingFolderSize = true;
KFileItemList list;
list.append( KFileItem( S_IFDIR, KFileItem::Unknown, location() + "/cur" ) );
list.append( KFileItem( S_IFDIR, KFileItem::Unknown, location() + "/new" ) );
list.append( KFileItem( S_IFDIR, KFileItem::Unknown, location() + "/tmp" ) );
// using QPointer<const KMFolderMaildir> would be nicer, but QPointer does not support
// const types as template parameter; therefore we have to const_cast this
s_DirSizeJobQueue->append(
qMakePair( QPointer<KMFolderMaildir>( const_cast<KMFolderMaildir*>( this ) ), list ) );
// if there's only one entry in the queue then we can start
// a DirectorySizeJob right away
if ( s_DirSizeJobQueue->size() == 1 )
{
//kDebug(5006) << "Starting DirectorySizeJob for folder" << location();
KIO::DirectorySizeJob* job = KIO::directorySize( list );
connect( job, SIGNAL( result( KJob* ) ),
this, SLOT( slotDirSizeJobResult( KJob*) ) );
}
return -1;
}
void KMFolderMaildir::slotDirSizeJobResult( KJob* job )
{
mCurrentlyCheckingFolderSize = false;
KIO::DirectorySizeJob * dirsize = dynamic_cast<KIO::DirectorySizeJob*>( job );
if ( dirsize && !dirsize->error() )
{
mSize = dirsize->totalSize();
//kDebug(5006) << << "DirectorySizeJob completed. Folder"
// << location() << "has size" << mSize;
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
//kDebug(5006) << "Starting DirectorySizeJob for folder"
// << entry.first->location();
KIO::DirectorySizeJob* job = KIO::directorySize( entry.second );
connect( job, SIGNAL( result( KJob* ) ),
entry.first, SLOT( slotDirSizeJobResult( KJob*) ) );
break;
}
else
{
// remove the invalid entry from the queue
s_DirSizeJobQueue->pop_front();
}
}
}
#include "kmfoldermaildir.moc"