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.
663 lines
17 KiB
663 lines
17 KiB
/* kmail message dictionary */
|
|
/* Author: Ronen Tzur <rtzur@shani.net> */
|
|
|
|
#include "kmfolderindex.h"
|
|
#include "kmfolder.h"
|
|
#include "kmmsgdict.h"
|
|
#include "kmdict.h"
|
|
#include "globalsettings.h"
|
|
#include "folderstorage.h"
|
|
|
|
#include <tqfileinfo.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <kstaticdeleter.h>
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef HAVE_BYTESWAP_H
|
|
#include <byteswap.h>
|
|
#endif
|
|
|
|
// 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
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Current version of the .index.ids files
|
|
#define IDS_VERSION 1002
|
|
|
|
// The asterisk at the end is important
|
|
#define IDS_HEADER "# KMail-Index-IDs V%d\n*"
|
|
|
|
/**
|
|
* @short an entry in the global message dictionary consisting of a pointer
|
|
* to a folder and the index of a message in the folder.
|
|
*/
|
|
class KMMsgDictEntry : public KMDictItem
|
|
{
|
|
public:
|
|
KMMsgDictEntry(const KMFolder *aFolder, int aIndex)
|
|
: folder( aFolder ), index( aIndex )
|
|
{}
|
|
|
|
const KMFolder *folder;
|
|
int index;
|
|
};
|
|
|
|
/**
|
|
* @short A "reverse entry", consisting of an array of DictEntry pointers.
|
|
*
|
|
* Each folder (storage) holds such an entry. That's useful for looking up the
|
|
* serial number of a message at a certain index in the folder, since that is the
|
|
* key of these entries.
|
|
*/
|
|
class KMMsgDictREntry
|
|
{
|
|
public:
|
|
KMMsgDictREntry(int size = 0)
|
|
{
|
|
array.resize(size);
|
|
memset(array.data(), 0, array.size() * sizeof(KMMsgDictEntry *)); // faster than a loop
|
|
fp = 0;
|
|
swapByteOrder = false;
|
|
baseOffset = 0;
|
|
}
|
|
|
|
~KMMsgDictREntry()
|
|
{
|
|
array.resize(0);
|
|
if (fp)
|
|
fclose(fp);
|
|
}
|
|
|
|
void set(int index, KMMsgDictEntry *entry)
|
|
{
|
|
if (index >= 0) {
|
|
int size = array.size();
|
|
if (index >= size) {
|
|
int newsize = TQMAX(size + 25, index + 1);
|
|
array.resize(newsize);
|
|
for (int j = size; j < newsize; j++)
|
|
array.at(j) = 0;
|
|
}
|
|
array.at(index) = entry;
|
|
}
|
|
}
|
|
|
|
KMMsgDictEntry *get(int index)
|
|
{
|
|
if (index >= 0 && (unsigned)index < array.size())
|
|
return array.at(index);
|
|
return 0;
|
|
}
|
|
|
|
ulong getMsn(int index)
|
|
{
|
|
KMMsgDictEntry *entry = get(index);
|
|
if (entry)
|
|
return entry->key;
|
|
return 0;
|
|
}
|
|
|
|
int getRealSize()
|
|
{
|
|
int count = array.size() - 1;
|
|
while (count >= 0) {
|
|
if (array.at(count))
|
|
break;
|
|
count--;
|
|
}
|
|
return count + 1;
|
|
}
|
|
|
|
void sync()
|
|
{
|
|
fflush(fp);
|
|
}
|
|
|
|
public:
|
|
TQMemArray<KMMsgDictEntry *> array;
|
|
FILE *fp;
|
|
bool swapByteOrder;
|
|
off_t baseOffset;
|
|
};
|
|
|
|
|
|
static KStaticDeleter<KMMsgDict> msgDict_sd;
|
|
KMMsgDict * KMMsgDict::m_self = 0;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KMMsgDict::KMMsgDict()
|
|
{
|
|
int lastSizeOfDict = GlobalSettings::self()->msgDictSizeHint();
|
|
lastSizeOfDict = ( lastSizeOfDict * 11 ) / 10;
|
|
GlobalSettings::self()->setMsgDictSizeHint( 0 );
|
|
dict = new KMDict( lastSizeOfDict );
|
|
nextMsgSerNum = 1;
|
|
m_self = this;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KMMsgDict::~KMMsgDict()
|
|
{
|
|
delete dict;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const KMMsgDict* KMMsgDict::instance()
|
|
{
|
|
if ( !m_self ) {
|
|
msgDict_sd.setObject( m_self, new KMMsgDict() );
|
|
}
|
|
return m_self;
|
|
}
|
|
|
|
KMMsgDict* KMMsgDict::mutableInstance()
|
|
{
|
|
if ( !m_self ) {
|
|
msgDict_sd.setObject( m_self, new KMMsgDict() );
|
|
}
|
|
return m_self;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
unsigned long KMMsgDict::getNextMsgSerNum() {
|
|
unsigned long msn = nextMsgSerNum;
|
|
nextMsgSerNum++;
|
|
return msn;
|
|
}
|
|
|
|
void KMMsgDict::deleteRentry(KMMsgDictREntry *entry)
|
|
{
|
|
delete entry;
|
|
}
|
|
|
|
unsigned long KMMsgDict::insert(unsigned long msgSerNum,
|
|
const KMMsgBase *msg, int index)
|
|
{
|
|
unsigned long msn = msgSerNum;
|
|
if (!msn) {
|
|
msn = getNextMsgSerNum();
|
|
} else {
|
|
if (msn >= nextMsgSerNum)
|
|
nextMsgSerNum = msn + 1;
|
|
}
|
|
|
|
KMFolderIndex* folder = static_cast<KMFolderIndex*>( msg->storage() );
|
|
if ( !folder ) {
|
|
kdDebug(5006) << "KMMsgDict::insert: Cannot insert the message, "
|
|
<< "null pointer to storage. Requested serial: " << msgSerNum
|
|
<< endl;
|
|
kdDebug(5006) << " Message info: Subject: " << msg->subject() << ", To: "
|
|
<< msg->toStrip() << ", Date: " << msg->dateStr() << endl;
|
|
return 0;
|
|
}
|
|
|
|
if (index == -1)
|
|
index = folder->tqfind(msg);
|
|
|
|
// Should not happen, indicates id file corruption
|
|
while (dict->tqfind((long)msn)) {
|
|
msn = getNextMsgSerNum();
|
|
folder->setDirty( true ); // rewrite id file
|
|
}
|
|
|
|
// Insert into the dict. Don't use dict->tqreplace() as we _know_
|
|
// there is no entry with the same msn, we just made sure.
|
|
KMMsgDictEntry *entry = new KMMsgDictEntry(folder->folder(), index);
|
|
dict->insert((long)msn, entry);
|
|
|
|
KMMsgDictREntry *rentry = folder->rDict();
|
|
if (!rentry) {
|
|
rentry = new KMMsgDictREntry();
|
|
folder->setRDict(rentry);
|
|
}
|
|
rentry->set(index, entry);
|
|
|
|
return msn;
|
|
}
|
|
|
|
unsigned long KMMsgDict::insert(const KMMsgBase *msg, int index)
|
|
{
|
|
unsigned long msn = msg->getMsgSerNum();
|
|
return insert(msn, msg, index);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KMMsgDict::tqreplace(unsigned long msgSerNum,
|
|
const KMMsgBase *msg, int index)
|
|
{
|
|
KMFolderIndex* folder = static_cast<KMFolderIndex*>( msg->storage() );
|
|
if ( !folder ) {
|
|
kdDebug(5006) << "KMMsgDict::tqreplace: Cannot replace the message serial "
|
|
<< "number, null pointer to storage. Requested serial: " << msgSerNum
|
|
<< endl;
|
|
kdDebug(5006) << " Message info: Subject: " << msg->subject() << ", To: "
|
|
<< msg->toStrip() << ", Date: " << msg->dateStr() << endl;
|
|
return;
|
|
}
|
|
|
|
if ( index == -1 )
|
|
index = folder->tqfind( msg );
|
|
|
|
remove( msgSerNum );
|
|
KMMsgDictEntry *entry = new KMMsgDictEntry( folder->folder(), index );
|
|
dict->insert( (long)msgSerNum, entry );
|
|
|
|
KMMsgDictREntry *rentry = folder->rDict();
|
|
if (!rentry) {
|
|
rentry = new KMMsgDictREntry();
|
|
folder->setRDict(rentry);
|
|
}
|
|
rentry->set(index, entry);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KMMsgDict::remove(unsigned long msgSerNum)
|
|
{
|
|
long key = (long)msgSerNum;
|
|
KMMsgDictEntry *entry = (KMMsgDictEntry *)dict->tqfind(key);
|
|
if (!entry)
|
|
return;
|
|
|
|
if (entry->folder) {
|
|
KMMsgDictREntry *rentry = entry->folder->storage()->rDict();
|
|
if (rentry)
|
|
rentry->set(entry->index, 0);
|
|
}
|
|
|
|
dict->remove((long)key);
|
|
}
|
|
|
|
unsigned long KMMsgDict::remove(const KMMsgBase *msg)
|
|
{
|
|
unsigned long msn = msg->getMsgSerNum();
|
|
remove(msn);
|
|
return msn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KMMsgDict::update(const KMMsgBase *msg, int index, int newIndex)
|
|
{
|
|
KMMsgDictREntry *rentry = msg->tqparent()->storage()->rDict();
|
|
if (rentry) {
|
|
KMMsgDictEntry *entry = rentry->get(index);
|
|
if (entry) {
|
|
entry->index = newIndex;
|
|
rentry->set(index, 0);
|
|
rentry->set(newIndex, entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void KMMsgDict::getLocation(unsigned long key,
|
|
KMFolder **retFolder, int *retIndex) const
|
|
{
|
|
KMMsgDictEntry *entry = (KMMsgDictEntry *)dict->tqfind((long)key);
|
|
if (entry) {
|
|
*retFolder = (KMFolder *)entry->folder;
|
|
*retIndex = entry->index;
|
|
} else {
|
|
*retFolder = 0;
|
|
*retIndex = -1;
|
|
}
|
|
}
|
|
|
|
void KMMsgDict::getLocation(const KMMsgBase *msg,
|
|
KMFolder **retFolder, int *retIndex) const
|
|
{
|
|
getLocation(msg->getMsgSerNum(), retFolder, retIndex);
|
|
}
|
|
|
|
void KMMsgDict::getLocation( const KMMessage * msg, KMFolder * *retFolder, int * retIndex ) const
|
|
{
|
|
getLocation( msg->toMsgBase().getMsgSerNum(), retFolder, retIndex );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
unsigned long KMMsgDict::getMsgSerNum(KMFolder *folder, int index) const
|
|
{
|
|
unsigned long msn = 0;
|
|
if ( folder ) {
|
|
KMMsgDictREntry *rentry = folder->storage()->rDict();
|
|
if (rentry)
|
|
msn = rentry->getMsn(index);
|
|
}
|
|
return msn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//static
|
|
TQValueList<unsigned long> KMMsgDict::serNumList(TQPtrList<KMMsgBase> msgList)
|
|
{
|
|
TQValueList<unsigned long> ret;
|
|
for ( unsigned int i = 0; i < msgList.count(); i++ ) {
|
|
unsigned long serNum = msgList.at(i)->getMsgSerNum();
|
|
assert( serNum );
|
|
ret.append( serNum );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TQString KMMsgDict::getFolderIdsLocation( const FolderStorage &storage )
|
|
{
|
|
return storage.indexLocation() + ".ids";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KMMsgDict::isFolderIdsOutdated( const FolderStorage &storage )
|
|
{
|
|
bool outdated = false;
|
|
|
|
TQFileInfo indexInfo( storage.indexLocation() );
|
|
TQFileInfo idsInfo( getFolderIdsLocation( storage ) );
|
|
|
|
if (!indexInfo.exists() || !idsInfo.exists())
|
|
outdated = true;
|
|
if (indexInfo.lastModified() > idsInfo.lastModified())
|
|
outdated = true;
|
|
|
|
return outdated;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int KMMsgDict::readFolderIds( FolderStorage& storage )
|
|
{
|
|
if ( isFolderIdsOutdated( storage ) )
|
|
return -1;
|
|
|
|
TQString filename = getFolderIdsLocation( storage );
|
|
FILE *fp = fopen(TQFile::encodeName(filename), "r+");
|
|
if (!fp)
|
|
return -1;
|
|
|
|
int version = 0;
|
|
fscanf(fp, IDS_HEADER, &version);
|
|
if (version != IDS_VERSION) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
|
|
bool swapByteOrder;
|
|
TQ_UINT32 byte_order;
|
|
if (!fread(&byte_order, sizeof(byte_order), 1, fp)) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
swapByteOrder = (byte_order == 0x78563412);
|
|
|
|
TQ_UINT32 count;
|
|
if (!fread(&count, sizeof(count), 1, fp)) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
if (swapByteOrder)
|
|
count = kmail_swap_32(count);
|
|
|
|
// quick consistency check to avoid allocating huge amount of memory
|
|
// due to reading corrupt file (#71549)
|
|
long pos = ftell(fp); // store current position
|
|
fseek(fp, 0, SEEK_END);
|
|
long fileSize = ftell(fp); // how large is the file ?
|
|
fseek(fp, pos, SEEK_SET); // back to previous position
|
|
|
|
// the file must at least contain what we try to read below
|
|
if ( (fileSize - pos) < (long)(count * sizeof(TQ_UINT32)) ) {
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
|
|
KMMsgDictREntry *rentry = new KMMsgDictREntry(count);
|
|
|
|
for (unsigned int index = 0; index < count; index++) {
|
|
TQ_UINT32 msn;
|
|
|
|
bool readOk = fread(&msn, sizeof(msn), 1, fp);
|
|
if (swapByteOrder)
|
|
msn = kmail_swap_32(msn);
|
|
|
|
if (!readOk || dict->tqfind(msn)) {
|
|
for (unsigned int i = 0; i < index; i++) {
|
|
msn = rentry->getMsn(i);
|
|
dict->remove((long)msn);
|
|
}
|
|
delete rentry;
|
|
fclose(fp);
|
|
return -1;
|
|
}
|
|
|
|
// We found a serial number that is zero. This is not allowed, and would
|
|
// later cause problems like in bug 149715.
|
|
// Therefore, use a fresh serial number instead
|
|
if ( msn == 0 ) {
|
|
kdWarning(5006) << "readFolderIds(): Found serial number zero at index " << index
|
|
<< " in folder " << filename << endl;
|
|
msn = getNextMsgSerNum();
|
|
Q_ASSERT( msn != 0 );
|
|
}
|
|
|
|
// Insert into the dict. Don't use dict->tqreplace() as we _know_
|
|
// there is no entry with the same msn, we just made sure.
|
|
KMMsgDictEntry *entry = new KMMsgDictEntry( storage.folder(), index);
|
|
dict->insert((long)msn, entry);
|
|
if (msn >= nextMsgSerNum)
|
|
nextMsgSerNum = msn + 1;
|
|
|
|
rentry->set(index, entry);
|
|
}
|
|
// Remember how many items we put into the dict this time so we can create
|
|
// it with an appropriate size next time.
|
|
GlobalSettings::setMsgDictSizeHint( GlobalSettings::msgDictSizeHint() + count );
|
|
|
|
fclose(fp);
|
|
storage.setRDict(rentry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
KMMsgDictREntry *KMMsgDict::openFolderIds( const FolderStorage& storage, bool truncate)
|
|
{
|
|
KMMsgDictREntry *rentry = storage.rDict();
|
|
if (!rentry) {
|
|
rentry = new KMMsgDictREntry();
|
|
storage.setRDict(rentry);
|
|
}
|
|
|
|
if (!rentry->fp) {
|
|
TQString filename = getFolderIdsLocation( storage );
|
|
FILE *fp = truncate ? 0 : fopen(TQFile::encodeName(filename), "r+");
|
|
if (fp)
|
|
{
|
|
int version = 0;
|
|
fscanf(fp, IDS_HEADER, &version);
|
|
if (version == IDS_VERSION)
|
|
{
|
|
TQ_UINT32 byte_order = 0;
|
|
fread(&byte_order, sizeof(byte_order), 1, fp);
|
|
rentry->swapByteOrder = (byte_order == 0x78563412);
|
|
}
|
|
else
|
|
{
|
|
fclose(fp);
|
|
fp = 0;
|
|
}
|
|
}
|
|
|
|
if (!fp)
|
|
{
|
|
fp = fopen(TQFile::encodeName(filename), "w+");
|
|
if (!fp)
|
|
{
|
|
kdDebug(5006) << "Dict '" << filename
|
|
<< "' cannot open with folder " << storage.label() << ": "
|
|
<< strerror(errno) << " (" << errno << ")" << endl;
|
|
delete rentry;
|
|
rentry = 0;
|
|
return 0;
|
|
}
|
|
fprintf(fp, IDS_HEADER, IDS_VERSION);
|
|
TQ_UINT32 byteOrder = 0x12345678;
|
|
fwrite(&byteOrder, sizeof(byteOrder), 1, fp);
|
|
rentry->swapByteOrder = false;
|
|
}
|
|
rentry->baseOffset = ftell(fp);
|
|
rentry->fp = fp;
|
|
}
|
|
|
|
return rentry;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int KMMsgDict::writeFolderIds( const FolderStorage &storage )
|
|
{
|
|
KMMsgDictREntry *rentry = openFolderIds( storage, true );
|
|
if (!rentry)
|
|
return 0;
|
|
FILE *fp = rentry->fp;
|
|
|
|
fseek(fp, rentry->baseOffset, SEEK_SET);
|
|
// kdDebug(5006) << "Dict writing for folder " << storage.label() << endl;
|
|
TQ_UINT32 count = rentry->getRealSize();
|
|
if (!fwrite(&count, sizeof(count), 1, fp)) {
|
|
kdDebug(5006) << "Dict cannot write count with folder " << storage.label() << ": "
|
|
<< strerror(errno) << " (" << errno << ")" << endl;
|
|
return -1;
|
|
}
|
|
|
|
for (unsigned int index = 0; index < count; index++) {
|
|
TQ_UINT32 msn = rentry->getMsn(index);
|
|
if (!fwrite(&msn, sizeof(msn), 1, fp))
|
|
return -1;
|
|
if ( msn == 0 ) {
|
|
kdWarning(5006) << "writeFolderIds(): Serial number of message at index "
|
|
<< index << " is zero in folder " << storage.label() << endl;
|
|
}
|
|
}
|
|
|
|
rentry->sync();
|
|
|
|
off_t eof = ftell(fp);
|
|
TQString filename = getFolderIdsLocation( storage );
|
|
truncate(TQFile::encodeName(filename), eof);
|
|
fclose(rentry->fp);
|
|
rentry->fp = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int KMMsgDict::touchFolderIds( const FolderStorage &storage )
|
|
{
|
|
KMMsgDictREntry *rentry = openFolderIds( storage, false);
|
|
if (rentry) {
|
|
rentry->sync();
|
|
fclose(rentry->fp);
|
|
rentry->fp = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
int KMMsgDict::appendToFolderIds( FolderStorage& storage, int index)
|
|
{
|
|
KMMsgDictREntry *rentry = openFolderIds( storage, false);
|
|
if (!rentry)
|
|
return 0;
|
|
FILE *fp = rentry->fp;
|
|
|
|
// kdDebug(5006) << "Dict appending for folder " << storage.label() << endl;
|
|
|
|
fseek(fp, rentry->baseOffset, SEEK_SET);
|
|
TQ_UINT32 count;
|
|
if (!fread(&count, sizeof(count), 1, fp)) {
|
|
kdDebug(5006) << "Dict cannot read count for folder " << storage.label() << ": "
|
|
<< strerror(errno) << " (" << errno << ")" << endl;
|
|
return 0;
|
|
}
|
|
if (rentry->swapByteOrder)
|
|
count = kmail_swap_32(count);
|
|
|
|
count++;
|
|
|
|
if (rentry->swapByteOrder)
|
|
count = kmail_swap_32(count);
|
|
fseek(fp, rentry->baseOffset, SEEK_SET);
|
|
if (!fwrite(&count, sizeof(count), 1, fp)) {
|
|
kdDebug(5006) << "Dict cannot write count for folder " << storage.label() << ": "
|
|
<< strerror(errno) << " (" << errno << ")" << endl;
|
|
return 0;
|
|
}
|
|
|
|
long ofs = (count - 1) * sizeof(ulong);
|
|
if (ofs > 0)
|
|
fseek(fp, ofs, SEEK_CUR);
|
|
|
|
TQ_UINT32 msn = rentry->getMsn(index);
|
|
if (rentry->swapByteOrder)
|
|
msn = kmail_swap_32(msn);
|
|
if (!fwrite(&msn, sizeof(msn), 1, fp)) {
|
|
kdDebug(5006) << "Dict cannot write count for folder " << storage.label() << ": "
|
|
<< strerror(errno) << " (" << errno << ")" << endl;
|
|
return 0;
|
|
}
|
|
|
|
rentry->sync();
|
|
fclose(rentry->fp);
|
|
rentry->fp = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KMMsgDict::hasFolderIds( const FolderStorage& storage )
|
|
{
|
|
return storage.rDict() != 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool KMMsgDict::removeFolderIds( FolderStorage& storage )
|
|
{
|
|
storage.setRDict( 0 );
|
|
TQString filename = getFolderIdsLocation( storage );
|
|
return unlink( TQFile::encodeName( filename) );
|
|
}
|