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.
578 lines
16 KiB
578 lines
16 KiB
/* -*- mode: C++; c-file-style: "gnu" -*-
|
|
This file is part of KMail, the KDE mail client.
|
|
Copyright (c) 2000 Don Sanders <sanders@kde.org>
|
|
|
|
KMail is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License, version 2, as
|
|
published by the Free Software Foundation.
|
|
|
|
KMail 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 "kmfolderindex.h"
|
|
#include "kmfolder.h"
|
|
#include "kmfoldertype.h"
|
|
#include "kcursorsaver.h"
|
|
#include <config.h>
|
|
#include <tqfileinfo.h>
|
|
#include <tqtimer.h>
|
|
#include <kdebug.h>
|
|
|
|
|
|
#define HAVE_MMAP //need to get this into autoconf FIXME --Sam
|
|
#include <unistd.h>
|
|
#ifdef HAVE_MMAP
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
// Current version of the table of contents (index) files
|
|
#define INDEX_VERSION 1507
|
|
|
|
#ifndef MAX_LINE
|
|
#define MAX_LINE 4096
|
|
#endif
|
|
|
|
#ifndef INIT_MSGS
|
|
#define INIT_MSGS 8
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <utime.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifdef HAVE_BYTESWAP_H
|
|
#include <byteswap.h>
|
|
#endif
|
|
#include <kapplication.h>
|
|
#include <kcursor.h>
|
|
#include <kmessagebox.h>
|
|
#include <klocale.h>
|
|
#include "kmmsgdict.h"
|
|
|
|
// We define functions as kmail_swap_NN so that we don't get compile errors
|
|
// on platforms where bswap_NN happens to be a function instead of a define.
|
|
|
|
/* Swap bytes in 32 bit value. */
|
|
#ifdef bswap_32
|
|
#define kmail_swap_32(x) bswap_32(x)
|
|
#else
|
|
#define kmail_swap_32(x) \
|
|
((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
|
|
(((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/file.h>
|
|
|
|
KMFolderIndex::KMFolderIndex(KMFolder* folder, const char* name)
|
|
: FolderStorage(folder, name), mMsgList(INIT_MSGS)
|
|
{
|
|
mIndexStream = 0;
|
|
mIndexStreamPtr = 0;
|
|
mIndexStreamPtrLength = 0;
|
|
mIndexSwapByteOrder = false;
|
|
mIndexSizeOfLong = sizeof(long);
|
|
mIndexId = 0;
|
|
mHeaderOffset = 0;
|
|
}
|
|
|
|
|
|
KMFolderIndex::~KMFolderIndex()
|
|
{
|
|
}
|
|
|
|
|
|
TQString KMFolderIndex::indexLocation() const
|
|
{
|
|
TQString sLocation(folder()->path());
|
|
|
|
if ( !sLocation.isEmpty() ) {
|
|
sLocation += '/';
|
|
sLocation += '.';
|
|
}
|
|
sLocation += dotEscape(fileName());
|
|
sLocation += ".index";
|
|
|
|
return sLocation;
|
|
}
|
|
|
|
int KMFolderIndex::updateIndex()
|
|
{
|
|
if (!mAutoCreateIndex)
|
|
return 0;
|
|
bool dirty = mDirty;
|
|
mDirtyTimer->stop();
|
|
for ( unsigned int i = 0; !dirty && i < mMsgList.high(); i++ ) {
|
|
if ( mMsgList.at(i) ) {
|
|
if ( !mMsgList.at(i)->syncIndexString() ) {
|
|
dirty = true;
|
|
}
|
|
}
|
|
}
|
|
if (!dirty) { // Update successful
|
|
touchFolderIdsFile();
|
|
return 0;
|
|
}
|
|
return writeIndex();
|
|
}
|
|
|
|
int KMFolderIndex::writeIndex( bool createEmptyIndex )
|
|
{
|
|
TQString tempName;
|
|
TQString indexName;
|
|
mode_t old_umask;
|
|
int len;
|
|
const uchar *buffer = 0;
|
|
|
|
indexName = indexLocation();
|
|
tempName = indexName + ".temp";
|
|
unlink(TQFile::encodeName(tempName));
|
|
|
|
// We touch the folder, otherwise the index is regenerated, if KMail is
|
|
// running, while the clock switches from daylight savings time to normal time
|
|
utime(TQFile::encodeName(location()), 0);
|
|
|
|
old_umask = umask(077);
|
|
FILE *tmpIndexStream = fopen(TQFile::encodeName(tempName), "w");
|
|
umask(old_umask);
|
|
if (!tmpIndexStream)
|
|
return errno;
|
|
|
|
fprintf(tmpIndexStream, "# KMail-Index V%d\n", INDEX_VERSION);
|
|
|
|
// Header
|
|
TQ_UINT32 byteOrder = 0x12345678;
|
|
TQ_UINT32 sizeOfLong = sizeof(long);
|
|
|
|
TQ_UINT32 header_length = sizeof(byteOrder)+sizeof(sizeOfLong);
|
|
char pad_char = '\0';
|
|
fwrite(&pad_char, sizeof(pad_char), 1, tmpIndexStream);
|
|
fwrite(&header_length, sizeof(header_length), 1, tmpIndexStream);
|
|
|
|
// Write header
|
|
fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream);
|
|
fwrite(&sizeOfLong, sizeof(sizeOfLong), 1, tmpIndexStream);
|
|
|
|
off_t nho = ftell(tmpIndexStream);
|
|
|
|
if ( !createEmptyIndex ) {
|
|
KMMsgBase* msgBase;
|
|
for (unsigned int i=0; i<mMsgList.high(); i++)
|
|
{
|
|
if (!(msgBase = mMsgList.at(i))) continue;
|
|
buffer = msgBase->asIndexString(len);
|
|
fwrite(&len,sizeof(len), 1, tmpIndexStream);
|
|
|
|
off_t tmp = ftell(tmpIndexStream);
|
|
msgBase->setIndexOffset(tmp);
|
|
msgBase->setIndexLength(len);
|
|
if(fwrite(buffer, len, 1, tmpIndexStream) != 1)
|
|
kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
|
|
}
|
|
}
|
|
|
|
int fError = ferror( tmpIndexStream );
|
|
if( fError != 0 ) {
|
|
fclose( tmpIndexStream );
|
|
return fError;
|
|
}
|
|
if( ( fflush( tmpIndexStream ) != 0 )
|
|
|| ( fsync( fileno( tmpIndexStream ) ) != 0 ) ) {
|
|
int errNo = errno;
|
|
fclose( tmpIndexStream );
|
|
return errNo;
|
|
}
|
|
if( fclose( tmpIndexStream ) != 0 )
|
|
return errno;
|
|
|
|
::rename(TQFile::encodeName(tempName), TQFile::encodeName(indexName));
|
|
mHeaderOffset = nho;
|
|
if (mIndexStream)
|
|
fclose(mIndexStream);
|
|
|
|
if ( createEmptyIndex )
|
|
return 0;
|
|
|
|
mIndexStream = fopen(TQFile::encodeName(indexName), "r+"); // index file
|
|
assert( mIndexStream );
|
|
fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
|
|
|
|
updateIndexStreamPtr();
|
|
|
|
writeFolderIdsFile();
|
|
|
|
setDirty( false );
|
|
return 0;
|
|
}
|
|
|
|
bool KMFolderIndex::readIndex()
|
|
{
|
|
if ( contentsType() != KMail::ContentsTypeMail ) {
|
|
kdDebug(5006) << k_funcinfo << "Reading index for " << label() << endl;
|
|
}
|
|
TQ_INT32 len;
|
|
KMMsgInfo* mi;
|
|
|
|
assert(mIndexStream != 0);
|
|
rewind(mIndexStream);
|
|
|
|
clearIndex();
|
|
int version;
|
|
|
|
setDirty( false );
|
|
|
|
if (!readIndexHeader(&version)) return false;
|
|
//kdDebug(5006) << "Index version for " << label() << " is " << version << endl;
|
|
|
|
mUnreadMsgs = 0;
|
|
mTotalMsgs = 0;
|
|
mHeaderOffset = ftell(mIndexStream);
|
|
|
|
clearIndex();
|
|
while (!feof(mIndexStream))
|
|
{
|
|
mi = 0;
|
|
if(version >= 1505) {
|
|
if(!fread(&len, sizeof(len), 1, mIndexStream)) {
|
|
// Seems to be normal?
|
|
// kdDebug(5006) << k_funcinfo << " Unable to read length field!" << endl;
|
|
break;
|
|
}
|
|
|
|
if (mIndexSwapByteOrder)
|
|
len = kmail_swap_32(len);
|
|
|
|
off_t offs = ftell(mIndexStream);
|
|
if(fseek(mIndexStream, len, SEEK_CUR)) {
|
|
kdDebug(5006) << k_funcinfo << " Unable to seek to the end of the message!" << endl;
|
|
break;
|
|
}
|
|
mi = new KMMsgInfo(folder(), offs, len);
|
|
}
|
|
else
|
|
{
|
|
TQCString line(MAX_LINE);
|
|
fgets(line.data(), MAX_LINE, mIndexStream);
|
|
if (feof(mIndexStream)) break;
|
|
if (*line.data() == '\0') {
|
|
fclose(mIndexStream);
|
|
mIndexStream = 0;
|
|
clearIndex();
|
|
return false;
|
|
}
|
|
mi = new KMMsgInfo(folder());
|
|
mi->compat_fromOldIndexString(line, mConvertToUtf8);
|
|
}
|
|
if(!mi) {
|
|
kdDebug(5006) << k_funcinfo << " Unable to create message info object!" << endl;
|
|
break;
|
|
}
|
|
|
|
if (mi->isDeleted())
|
|
{
|
|
delete mi; // skip messages that are marked as deleted
|
|
setDirty( true );
|
|
needsCompact = true; //We have deleted messages - needs to be compacted
|
|
continue;
|
|
}
|
|
#ifdef OBSOLETE
|
|
else if (mi->isNew())
|
|
{
|
|
mi->setStatus(KMMsgStatusUnread);
|
|
mi->setDirty(false);
|
|
}
|
|
#endif
|
|
if ((mi->isNew()) || (mi->isUnread()) ||
|
|
(folder() == kmkernel->outboxFolder()))
|
|
{
|
|
++mUnreadMsgs;
|
|
if (mUnreadMsgs == 0) ++mUnreadMsgs;
|
|
}
|
|
mMsgList.append(mi, false);
|
|
}
|
|
if( version < 1505)
|
|
{
|
|
mConvertToUtf8 = false;
|
|
setDirty( true );
|
|
writeIndex();
|
|
}
|
|
|
|
if ( version < 1507 ) {
|
|
updateInvitationAndAddressFieldsFromContents();
|
|
setDirty( true );
|
|
writeIndex();
|
|
}
|
|
|
|
mTotalMsgs = mMsgList.count();
|
|
if ( contentsType() != KMail::ContentsTypeMail ) {
|
|
kdDebug(5006) << k_funcinfo << "Done reading the index for " << label() << ", we have " << mTotalMsgs << " messages." << endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
int KMFolderIndex::count(bool cache) const
|
|
{
|
|
int res = FolderStorage::count(cache);
|
|
if (res == -1)
|
|
res = mMsgList.count();
|
|
return res;
|
|
}
|
|
|
|
|
|
bool KMFolderIndex::readIndexHeader(int *gv)
|
|
{
|
|
int indexVersion;
|
|
assert(mIndexStream != 0);
|
|
mIndexSwapByteOrder = false;
|
|
mIndexSizeOfLong = sizeof(long);
|
|
|
|
int ret = fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion);
|
|
if ( ret == EOF || ret == 0 )
|
|
return false; // index file has invalid header
|
|
if(gv)
|
|
*gv = indexVersion;
|
|
|
|
// Check if the index is corrupted ("not compactable") and recreate it if necessary. See
|
|
// FolderStorage::getMsg() for the detection code.
|
|
if ( !mCompactable ) {
|
|
kdWarning(5006) << "Index file " << indexLocation() << " is corrupted!!. Re-creating it." << endl;
|
|
recreateIndex( false /* don't call readIndex() afterwards */ );
|
|
return false;
|
|
}
|
|
|
|
if (indexVersion < 1505 ) {
|
|
if(indexVersion == 1503) {
|
|
kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl;
|
|
mConvertToUtf8 = true;
|
|
}
|
|
return true;
|
|
} else if (indexVersion == 1505) {
|
|
} else if (indexVersion < INDEX_VERSION && indexVersion != 1506) {
|
|
kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl;
|
|
createIndexFromContents();
|
|
return false;
|
|
} else if(indexVersion > INDEX_VERSION) {
|
|
kapp->setOverrideCursor(KCursor::arrowCursor());
|
|
int r = KMessageBox::questionYesNo(0,
|
|
i18n(
|
|
"The mail index for '%1' is from an unknown version of KMail (%2).\n"
|
|
"This index can be regenerated from your mail folder, but some "
|
|
"information, including status flags, may be lost. Do you wish "
|
|
"to downgrade your index file?") .arg(name()) .arg(indexVersion), TQString(), i18n("Downgrade"), i18n("Do Not Downgrade") );
|
|
kapp->restoreOverrideCursor();
|
|
if (r == KMessageBox::Yes)
|
|
createIndexFromContents();
|
|
return false;
|
|
}
|
|
else {
|
|
// Header
|
|
TQ_UINT32 byteOrder = 0;
|
|
TQ_UINT32 sizeOfLong = sizeof(long); // default
|
|
|
|
TQ_UINT32 header_length = 0;
|
|
fseek(mIndexStream, sizeof(char), SEEK_CUR );
|
|
fread(&header_length, sizeof(header_length), 1, mIndexStream);
|
|
if (header_length > 0xFFFF)
|
|
header_length = kmail_swap_32(header_length);
|
|
|
|
off_t endOfHeader = ftell(mIndexStream) + header_length;
|
|
|
|
bool needs_update = true;
|
|
// Process available header parts
|
|
if (header_length >= sizeof(byteOrder))
|
|
{
|
|
fread(&byteOrder, sizeof(byteOrder), 1, mIndexStream);
|
|
mIndexSwapByteOrder = (byteOrder == 0x78563412);
|
|
header_length -= sizeof(byteOrder);
|
|
|
|
if (header_length >= sizeof(sizeOfLong))
|
|
{
|
|
fread(&sizeOfLong, sizeof(sizeOfLong), 1, mIndexStream);
|
|
if (mIndexSwapByteOrder)
|
|
sizeOfLong = kmail_swap_32(sizeOfLong);
|
|
mIndexSizeOfLong = sizeOfLong;
|
|
header_length -= sizeof(sizeOfLong);
|
|
needs_update = false;
|
|
}
|
|
}
|
|
if (needs_update || mIndexSwapByteOrder || (mIndexSizeOfLong != sizeof(long)))
|
|
setDirty( true );
|
|
// Seek to end of header
|
|
fseek(mIndexStream, endOfHeader, SEEK_SET );
|
|
|
|
if (mIndexSwapByteOrder)
|
|
kdDebug(5006) << "Index File has byte order swapped!" << endl;
|
|
if (mIndexSizeOfLong != sizeof(long))
|
|
kdDebug(5006) << "Index File sizeOfLong is " << mIndexSizeOfLong << " while sizeof(long) is " << sizeof(long) << " !" << endl;
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
#ifdef HAVE_MMAP
|
|
bool KMFolderIndex::updateIndexStreamPtr(bool just_close)
|
|
#else
|
|
bool KMFolderIndex::updateIndexStreamPtr(bool)
|
|
#endif
|
|
{
|
|
// We touch the folder, otherwise the index is regenerated, if KMail is
|
|
// running, while the clock switches from daylight savings time to normal time
|
|
utime(TQFile::encodeName(location()), 0);
|
|
utime(TQFile::encodeName(indexLocation()), 0);
|
|
utime(TQFile::encodeName( KMMsgDict::getFolderIdsLocation( *this ) ), 0);
|
|
|
|
mIndexSwapByteOrder = false;
|
|
#ifdef HAVE_MMAP
|
|
if(just_close) {
|
|
if(mIndexStreamPtr)
|
|
munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
|
|
mIndexStreamPtr = 0;
|
|
mIndexStreamPtrLength = 0;
|
|
return true;
|
|
}
|
|
|
|
assert(mIndexStream);
|
|
struct stat stat_buf;
|
|
if(fstat(fileno(mIndexStream), &stat_buf) == -1) {
|
|
if(mIndexStreamPtr)
|
|
munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
|
|
mIndexStreamPtr = 0;
|
|
mIndexStreamPtrLength = 0;
|
|
return false;
|
|
}
|
|
if(mIndexStreamPtr)
|
|
munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
|
|
mIndexStreamPtrLength = stat_buf.st_size;
|
|
mIndexStreamPtr = (uchar *)mmap(0, mIndexStreamPtrLength, PROT_READ, MAP_SHARED,
|
|
fileno(mIndexStream), 0);
|
|
if(mIndexStreamPtr == MAP_FAILED) {
|
|
mIndexStreamPtr = 0;
|
|
mIndexStreamPtrLength = 0;
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
KMFolderIndex::IndexStatus KMFolderIndex::indexStatus()
|
|
{
|
|
if ( !mCompactable )
|
|
return IndexCorrupt;
|
|
|
|
TQFileInfo contInfo(location());
|
|
TQFileInfo indInfo(indexLocation());
|
|
|
|
if (!contInfo.exists()) return KMFolderIndex::IndexOk;
|
|
if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
|
|
|
|
return ( contInfo.lastModified() > indInfo.lastModified() )
|
|
? KMFolderIndex::IndexTooOld
|
|
: KMFolderIndex::IndexOk;
|
|
}
|
|
|
|
void KMFolderIndex::clearIndex(bool autoDelete, bool syncDict)
|
|
{
|
|
mMsgList.clear(autoDelete, syncDict);
|
|
}
|
|
|
|
|
|
void KMFolderIndex::truncateIndex()
|
|
{
|
|
if ( mHeaderOffset )
|
|
truncate(TQFile::encodeName(indexLocation()), mHeaderOffset);
|
|
else
|
|
// The index file wasn't opened, so we don't know the header offset.
|
|
// So let's just create a new empty index.
|
|
writeIndex( true );
|
|
}
|
|
|
|
void KMFolderIndex::fillMessageDict()
|
|
{
|
|
open("fillDict");
|
|
for (unsigned int idx = 0; idx < mMsgList.high(); idx++)
|
|
if ( mMsgList.at( idx ) )
|
|
KMMsgDict::mutableInstance()->insert(0, mMsgList.at( idx ), idx);
|
|
close("fillDict");
|
|
}
|
|
|
|
|
|
KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg )
|
|
{
|
|
KMMsgInfo *msgInfo = msg->msgInfo();
|
|
if ( !msgInfo )
|
|
msgInfo = new KMMsgInfo( folder() );
|
|
|
|
*msgInfo = *msg;
|
|
mMsgList.set( idx, msgInfo );
|
|
msg->setMsgInfo( 0 );
|
|
delete msg;
|
|
return msgInfo;
|
|
}
|
|
|
|
void KMFolderIndex::recreateIndex( bool readIndexAfterwards )
|
|
{
|
|
kapp->setOverrideCursor(KCursor::arrowCursor());
|
|
KMessageBox::information(0,
|
|
i18n("The mail index for '%1' is corrupted and will be regenerated now, "
|
|
"but some information, like status flags, might get lost.").arg(name()));
|
|
kapp->restoreOverrideCursor();
|
|
createIndexFromContents();
|
|
if ( readIndexAfterwards ) {
|
|
readIndex();
|
|
}
|
|
|
|
// Clear the corrupted flag
|
|
mCompactable = true;
|
|
writeConfig();
|
|
}
|
|
|
|
void KMFolderIndex::silentlyRecreateIndex()
|
|
{
|
|
Q_ASSERT( !isOpened() );
|
|
open( "silentlyRecreateIndex" );
|
|
KCursorSaver busy( KBusyPtr::busy() );
|
|
createIndexFromContents();
|
|
mCompactable = true;
|
|
writeConfig();
|
|
close( "silentlyRecreateIndex" );
|
|
}
|
|
|
|
void KMFolderIndex::updateInvitationAndAddressFieldsFromContents()
|
|
{
|
|
kdDebug(5006) << "Updating index for " << label() << ", this might take a while." << endl;
|
|
for ( uint i = 0; i < mMsgList.size(); i++ ) {
|
|
KMMsgInfo * const msgInfo = dynamic_cast<KMMsgInfo*>( mMsgList[i] );
|
|
if ( msgInfo ) {
|
|
DwString msgString( getDwString( i ) );
|
|
if ( msgString.size() > 0 ) {
|
|
KMMessage msg;
|
|
msg.fromDwString( msgString, false );
|
|
msg.updateInvitationState();
|
|
if ( msg.status() & KMMsgStatusHasInvitation ) {
|
|
msgInfo->setStatus( msgInfo->status() | KMMsgStatusHasInvitation );
|
|
}
|
|
if ( msg.status() & KMMsgStatusHasNoInvitation ) {
|
|
msgInfo->setStatus( msgInfo->status() | KMMsgStatusHasNoInvitation );
|
|
}
|
|
msgInfo->setFrom( msg.from() );
|
|
msgInfo->setTo( msg.to() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "kmfolderindex.moc"
|