kmail

kmmsgdict.cpp
1/* kmail message dictionary */
2/* Author: Ronen Tzur <rtzur@shani.net> */
3
4#include "kmfolderindex.h"
5#include "kmfolder.h"
6#include "kmmsgdict.h"
7#include "kmdict.h"
8#include "globalsettings.h"
9#include "folderstorage.h"
10
11#include <tqfileinfo.h>
12
13#include <kdebug.h>
14#include <kstaticdeleter.h>
15
16#include <stdio.h>
17#include <unistd.h>
18
19#include <string.h>
20#include <errno.h>
21
22#include <config.h>
23
24#ifdef HAVE_BYTESWAP_H
25#include <byteswap.h>
26#endif
27
28// We define functions as kmail_swap_NN so that we don't get compile errors
29// on platforms where bswap_NN happens to be a function instead of a define.
30
31/* Swap bytes in 32 bit value. */
32#ifdef bswap_32
33#define kmail_swap_32(x) bswap_32(x)
34#else
35#define kmail_swap_32(x) \
36 ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
37 (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
38#endif
39
40
41//-----------------------------------------------------------------------------
42
43// Current version of the .index.ids files
44#define IDS_VERSION 1002
45
46// The asterisk at the end is important
47#define IDS_HEADER "# KMail-Index-IDs V%d\n*"
48
53class KMMsgDictEntry : public KMDictItem
54{
55public:
56 KMMsgDictEntry(const KMFolder *aFolder, int aIndex)
57 : folder( aFolder ), index( aIndex )
58 {}
59
60 const KMFolder *folder;
61 int index;
62};
63
71class KMMsgDictREntry
72{
73public:
74 KMMsgDictREntry(int size = 0)
75 {
76 array.resize(size);
77 memset(array.data(), 0, array.size() * sizeof(KMMsgDictEntry *)); // faster than a loop
78 fp = 0;
79 swapByteOrder = false;
80 baseOffset = 0;
81 }
82
83 ~KMMsgDictREntry()
84 {
85 array.resize(0);
86 if (fp)
87 fclose(fp);
88 }
89
90 void set(int index, KMMsgDictEntry *entry)
91 {
92 if (index >= 0) {
93 int size = array.size();
94 if (index >= size) {
95 int newsize = TQMAX(size + 25, index + 1);
96 array.resize(newsize);
97 for (int j = size; j < newsize; j++)
98 array.at(j) = 0;
99 }
100 array.at(index) = entry;
101 }
102 }
103
104 KMMsgDictEntry *get(int index)
105 {
106 if (index >= 0 && (unsigned)index < array.size())
107 return array.at(index);
108 return 0;
109 }
110
111 ulong getMsn(int index)
112 {
113 KMMsgDictEntry *entry = get(index);
114 if (entry)
115 return entry->key;
116 return 0;
117 }
118
119 int getRealSize()
120 {
121 int count = array.size() - 1;
122 while (count >= 0) {
123 if (array.at(count))
124 break;
125 count--;
126 }
127 return count + 1;
128 }
129
130 void sync()
131 {
132 fflush(fp);
133 }
134
135public:
136 TQMemArray<KMMsgDictEntry *> array;
137 FILE *fp;
138 bool swapByteOrder;
139 off_t baseOffset;
140};
141
142
143static KStaticDeleter<KMMsgDict> msgDict_sd;
144KMMsgDict * KMMsgDict::m_self = 0;
145
146//-----------------------------------------------------------------------------
147
148KMMsgDict::KMMsgDict()
149{
150 int lastSizeOfDict = GlobalSettings::self()->msgDictSizeHint();
151 lastSizeOfDict = ( lastSizeOfDict * 11 ) / 10;
152 GlobalSettings::self()->setMsgDictSizeHint( 0 );
153 dict = new KMDict( lastSizeOfDict );
154 nextMsgSerNum = 1;
155 m_self = this;
156}
157
158//-----------------------------------------------------------------------------
159
160KMMsgDict::~KMMsgDict()
161{
162 delete dict;
163}
164
165//-----------------------------------------------------------------------------
166
168{
169 if ( !m_self ) {
170 msgDict_sd.setObject( m_self, new KMMsgDict() );
171 }
172 return m_self;
173}
174
175KMMsgDict* KMMsgDict::mutableInstance()
176{
177 if ( !m_self ) {
178 msgDict_sd.setObject( m_self, new KMMsgDict() );
179 }
180 return m_self;
181}
182
183//-----------------------------------------------------------------------------
184
185unsigned long KMMsgDict::getNextMsgSerNum() {
186 unsigned long msn = nextMsgSerNum;
187 nextMsgSerNum++;
188 return msn;
189}
190
191void KMMsgDict::deleteRentry(KMMsgDictREntry *entry)
192{
193 delete entry;
194}
195
196unsigned long KMMsgDict::insert(unsigned long msgSerNum,
197 const KMMsgBase *msg, int index)
198{
199 unsigned long msn = msgSerNum;
200 if (!msn) {
201 msn = getNextMsgSerNum();
202 } else {
203 if (msn >= nextMsgSerNum)
204 nextMsgSerNum = msn + 1;
205 }
206
207 KMFolderIndex* folder = static_cast<KMFolderIndex*>( msg->storage() );
208 if ( !folder ) {
209 kdDebug(5006) << "KMMsgDict::insert: Cannot insert the message, "
210 << "null pointer to storage. Requested serial: " << msgSerNum
211 << endl;
212 kdDebug(5006) << " Message info: Subject: " << msg->subject() << ", To: "
213 << msg->toStrip() << ", Date: " << msg->dateStr() << endl;
214 return 0;
215 }
216
217 if (index == -1)
218 index = folder->find(msg);
219
220 // Should not happen, indicates id file corruption
221 while (dict->find((long)msn)) {
222 msn = getNextMsgSerNum();
223 folder->setDirty( true ); // rewrite id file
224 }
225
226 // Insert into the dict. Don't use dict->replace() as we _know_
227 // there is no entry with the same msn, we just made sure.
228 KMMsgDictEntry *entry = new KMMsgDictEntry(folder->folder(), index);
229 dict->insert((long)msn, entry);
230
231 KMMsgDictREntry *rentry = folder->rDict();
232 if (!rentry) {
233 rentry = new KMMsgDictREntry();
234 folder->setRDict(rentry);
235 }
236 rentry->set(index, entry);
237
238 return msn;
239}
240
241unsigned long KMMsgDict::insert(const KMMsgBase *msg, int index)
242{
243 unsigned long msn = msg->getMsgSerNum();
244 return insert(msn, msg, index);
245}
246
247//-----------------------------------------------------------------------------
248
249void KMMsgDict::replace(unsigned long msgSerNum,
250 const KMMsgBase *msg, int index)
251{
252 KMFolderIndex* folder = static_cast<KMFolderIndex*>( msg->storage() );
253 if ( !folder ) {
254 kdDebug(5006) << "KMMsgDict::replace: Cannot replace the message serial "
255 << "number, null pointer to storage. Requested serial: " << msgSerNum
256 << endl;
257 kdDebug(5006) << " Message info: Subject: " << msg->subject() << ", To: "
258 << msg->toStrip() << ", Date: " << msg->dateStr() << endl;
259 return;
260 }
261
262 if ( index == -1 )
263 index = folder->find( msg );
264
265 remove( msgSerNum );
266 KMMsgDictEntry *entry = new KMMsgDictEntry( folder->folder(), index );
267 dict->insert( (long)msgSerNum, entry );
268
269 KMMsgDictREntry *rentry = folder->rDict();
270 if (!rentry) {
271 rentry = new KMMsgDictREntry();
272 folder->setRDict(rentry);
273 }
274 rentry->set(index, entry);
275}
276
277//-----------------------------------------------------------------------------
278
279void KMMsgDict::remove(unsigned long msgSerNum)
280{
281 long key = (long)msgSerNum;
282 KMMsgDictEntry *entry = (KMMsgDictEntry *)dict->find(key);
283 if (!entry)
284 return;
285
286 if (entry->folder) {
287 KMMsgDictREntry *rentry = entry->folder->storage()->rDict();
288 if (rentry)
289 rentry->set(entry->index, 0);
290 }
291
292 dict->remove((long)key);
293}
294
295unsigned long KMMsgDict::remove(const KMMsgBase *msg)
296{
297 unsigned long msn = msg->getMsgSerNum();
298 remove(msn);
299 return msn;
300}
301
302//-----------------------------------------------------------------------------
303
304void KMMsgDict::update(const KMMsgBase *msg, int index, int newIndex)
305{
306 KMMsgDictREntry *rentry = msg->parent()->storage()->rDict();
307 if (rentry) {
308 KMMsgDictEntry *entry = rentry->get(index);
309 if (entry) {
310 entry->index = newIndex;
311 rentry->set(index, 0);
312 rentry->set(newIndex, entry);
313 }
314 }
315}
316
317//-----------------------------------------------------------------------------
318
319void KMMsgDict::getLocation(unsigned long key,
320 KMFolder **retFolder, int *retIndex) const
321{
322 KMMsgDictEntry *entry = (KMMsgDictEntry *)dict->find((long)key);
323 if (entry) {
324 *retFolder = (KMFolder *)entry->folder;
325 *retIndex = entry->index;
326 } else {
327 *retFolder = 0;
328 *retIndex = -1;
329 }
330}
331
332void KMMsgDict::getLocation(const KMMsgBase *msg,
333 KMFolder **retFolder, int *retIndex) const
334{
335 getLocation(msg->getMsgSerNum(), retFolder, retIndex);
336}
337
338void KMMsgDict::getLocation( const KMMessage * msg, KMFolder * *retFolder, int * retIndex ) const
339{
340 getLocation( msg->toMsgBase().getMsgSerNum(), retFolder, retIndex );
341}
342
343//-----------------------------------------------------------------------------
344
345unsigned long KMMsgDict::getMsgSerNum(KMFolder *folder, int index) const
346{
347 unsigned long msn = 0;
348 if ( folder ) {
349 KMMsgDictREntry *rentry = folder->storage()->rDict();
350 if (rentry)
351 msn = rentry->getMsn(index);
352 }
353 return msn;
354}
355
356//-----------------------------------------------------------------------------
357
358//static
359TQValueList<unsigned long> KMMsgDict::serNumList(TQPtrList<KMMsgBase> msgList)
360{
361 TQValueList<unsigned long> ret;
362 for ( unsigned int i = 0; i < msgList.count(); i++ ) {
363 unsigned long serNum = msgList.at(i)->getMsgSerNum();
364 assert( serNum );
365 ret.append( serNum );
366 }
367 return ret;
368}
369
370//-----------------------------------------------------------------------------
371
372TQString KMMsgDict::getFolderIdsLocation( const FolderStorage &storage )
373{
374 return storage.indexLocation() + ".ids";
375}
376
377//-----------------------------------------------------------------------------
378
379bool KMMsgDict::isFolderIdsOutdated( const FolderStorage &storage )
380{
381 bool outdated = false;
382
383 TQFileInfo indexInfo( storage.indexLocation() );
384 TQFileInfo idsInfo( getFolderIdsLocation( storage ) );
385
386 if (!indexInfo.exists() || !idsInfo.exists())
387 outdated = true;
388 if (indexInfo.lastModified() > idsInfo.lastModified())
389 outdated = true;
390
391 return outdated;
392}
393
394//-----------------------------------------------------------------------------
395
396int KMMsgDict::readFolderIds( FolderStorage& storage )
397{
398 if ( isFolderIdsOutdated( storage ) )
399 return -1;
400
401 TQString filename = getFolderIdsLocation( storage );
402 FILE *fp = fopen(TQFile::encodeName(filename), "r+");
403 if (!fp)
404 return -1;
405
406 int version = 0;
407 fscanf(fp, IDS_HEADER, &version);
408 if (version != IDS_VERSION) {
409 fclose(fp);
410 return -1;
411 }
412
413 bool swapByteOrder;
414 TQ_UINT32 byte_order;
415 if (!fread(&byte_order, sizeof(byte_order), 1, fp)) {
416 fclose(fp);
417 return -1;
418 }
419 swapByteOrder = (byte_order == 0x78563412);
420
421 TQ_UINT32 count;
422 if (!fread(&count, sizeof(count), 1, fp)) {
423 fclose(fp);
424 return -1;
425 }
426 if (swapByteOrder)
427 count = kmail_swap_32(count);
428
429 // quick consistency check to avoid allocating huge amount of memory
430 // due to reading corrupt file (#71549)
431 long pos = ftell(fp); // store current position
432 fseek(fp, 0, SEEK_END);
433 long fileSize = ftell(fp); // how large is the file ?
434 fseek(fp, pos, SEEK_SET); // back to previous position
435
436 // the file must at least contain what we try to read below
437 if ( (fileSize - pos) < (long)(count * sizeof(TQ_UINT32)) ) {
438 fclose(fp);
439 return -1;
440 }
441
442 KMMsgDictREntry *rentry = new KMMsgDictREntry(count);
443
444 for (unsigned int index = 0; index < count; index++) {
445 TQ_UINT32 msn;
446
447 bool readOk = fread(&msn, sizeof(msn), 1, fp);
448 if (swapByteOrder)
449 msn = kmail_swap_32(msn);
450
451 if (!readOk || dict->find(msn)) {
452 for (unsigned int i = 0; i < index; i++) {
453 msn = rentry->getMsn(i);
454 dict->remove((long)msn);
455 }
456 delete rentry;
457 fclose(fp);
458 return -1;
459 }
460
461 // We found a serial number that is zero. This is not allowed, and would
462 // later cause problems like in bug 149715.
463 // Therefore, use a fresh serial number instead
464 if ( msn == 0 ) {
465 kdWarning(5006) << "readFolderIds(): Found serial number zero at index " << index
466 << " in folder " << filename << endl;
467 msn = getNextMsgSerNum();
468 Q_ASSERT( msn != 0 );
469 }
470
471 // Insert into the dict. Don't use dict->replace() as we _know_
472 // there is no entry with the same msn, we just made sure.
473 KMMsgDictEntry *entry = new KMMsgDictEntry( storage.folder(), index);
474 dict->insert((long)msn, entry);
475 if (msn >= nextMsgSerNum)
476 nextMsgSerNum = msn + 1;
477
478 rentry->set(index, entry);
479 }
480 // Remember how many items we put into the dict this time so we can create
481 // it with an appropriate size next time.
482 GlobalSettings::setMsgDictSizeHint( GlobalSettings::msgDictSizeHint() + count );
483
484 fclose(fp);
485 storage.setRDict(rentry);
486
487 return 0;
488}
489
490//-----------------------------------------------------------------------------
491
492KMMsgDictREntry *KMMsgDict::openFolderIds( const FolderStorage& storage, bool truncate)
493{
494 KMMsgDictREntry *rentry = storage.rDict();
495 if (!rentry) {
496 rentry = new KMMsgDictREntry();
497 storage.setRDict(rentry);
498 }
499
500 if (!rentry->fp) {
501 TQString filename = getFolderIdsLocation( storage );
502 FILE *fp = truncate ? 0 : fopen(TQFile::encodeName(filename), "r+");
503 if (fp)
504 {
505 int version = 0;
506 fscanf(fp, IDS_HEADER, &version);
507 if (version == IDS_VERSION)
508 {
509 TQ_UINT32 byte_order = 0;
510 fread(&byte_order, sizeof(byte_order), 1, fp);
511 rentry->swapByteOrder = (byte_order == 0x78563412);
512 }
513 else
514 {
515 fclose(fp);
516 fp = 0;
517 }
518 }
519
520 if (!fp)
521 {
522 fp = fopen(TQFile::encodeName(filename), "w+");
523 if (!fp)
524 {
525 kdDebug(5006) << "Dict '" << filename
526 << "' cannot open with folder " << storage.label() << ": "
527 << strerror(errno) << " (" << errno << ")" << endl;
528 delete rentry;
529 rentry = 0;
530 return 0;
531 }
532 fprintf(fp, IDS_HEADER, IDS_VERSION);
533 TQ_UINT32 byteOrder = 0x12345678;
534 fwrite(&byteOrder, sizeof(byteOrder), 1, fp);
535 rentry->swapByteOrder = false;
536 }
537 rentry->baseOffset = ftell(fp);
538 rentry->fp = fp;
539 }
540
541 return rentry;
542}
543
544//-----------------------------------------------------------------------------
545
546int KMMsgDict::writeFolderIds( const FolderStorage &storage )
547{
548 KMMsgDictREntry *rentry = openFolderIds( storage, true );
549 if (!rentry)
550 return 0;
551 FILE *fp = rentry->fp;
552
553 fseek(fp, rentry->baseOffset, SEEK_SET);
554 // kdDebug(5006) << "Dict writing for folder " << storage.label() << endl;
555 TQ_UINT32 count = rentry->getRealSize();
556 if (!fwrite(&count, sizeof(count), 1, fp)) {
557 kdDebug(5006) << "Dict cannot write count with folder " << storage.label() << ": "
558 << strerror(errno) << " (" << errno << ")" << endl;
559 return -1;
560 }
561
562 for (unsigned int index = 0; index < count; index++) {
563 TQ_UINT32 msn = rentry->getMsn(index);
564 if (!fwrite(&msn, sizeof(msn), 1, fp))
565 return -1;
566 if ( msn == 0 ) {
567 kdWarning(5006) << "writeFolderIds(): Serial number of message at index "
568 << index << " is zero in folder " << storage.label() << endl;
569 }
570 }
571
572 rentry->sync();
573
574 off_t eof = ftell(fp);
575 TQString filename = getFolderIdsLocation( storage );
576 truncate(TQFile::encodeName(filename), eof);
577 fclose(rentry->fp);
578 rentry->fp = 0;
579
580 return 0;
581}
582
583//-----------------------------------------------------------------------------
584
585int KMMsgDict::touchFolderIds( const FolderStorage &storage )
586{
587 KMMsgDictREntry *rentry = openFolderIds( storage, false);
588 if (rentry) {
589 rentry->sync();
590 fclose(rentry->fp);
591 rentry->fp = 0;
592 }
593 return 0;
594}
595
596//-----------------------------------------------------------------------------
597
598int KMMsgDict::appendToFolderIds( FolderStorage& storage, int index)
599{
600 KMMsgDictREntry *rentry = openFolderIds( storage, false);
601 if (!rentry)
602 return 0;
603 FILE *fp = rentry->fp;
604
605// kdDebug(5006) << "Dict appending for folder " << storage.label() << endl;
606
607 fseek(fp, rentry->baseOffset, SEEK_SET);
608 TQ_UINT32 count;
609 if (!fread(&count, sizeof(count), 1, fp)) {
610 kdDebug(5006) << "Dict cannot read count for folder " << storage.label() << ": "
611 << strerror(errno) << " (" << errno << ")" << endl;
612 return 0;
613 }
614 if (rentry->swapByteOrder)
615 count = kmail_swap_32(count);
616
617 count++;
618
619 if (rentry->swapByteOrder)
620 count = kmail_swap_32(count);
621 fseek(fp, rentry->baseOffset, SEEK_SET);
622 if (!fwrite(&count, sizeof(count), 1, fp)) {
623 kdDebug(5006) << "Dict cannot write count for folder " << storage.label() << ": "
624 << strerror(errno) << " (" << errno << ")" << endl;
625 return 0;
626 }
627
628 long ofs = (count - 1) * sizeof(ulong);
629 if (ofs > 0)
630 fseek(fp, ofs, SEEK_CUR);
631
632 TQ_UINT32 msn = rentry->getMsn(index);
633 if (rentry->swapByteOrder)
634 msn = kmail_swap_32(msn);
635 if (!fwrite(&msn, sizeof(msn), 1, fp)) {
636 kdDebug(5006) << "Dict cannot write count for folder " << storage.label() << ": "
637 << strerror(errno) << " (" << errno << ")" << endl;
638 return 0;
639 }
640
641 rentry->sync();
642 fclose(rentry->fp);
643 rentry->fp = 0;
644
645 return 0;
646}
647
648//-----------------------------------------------------------------------------
649
650bool KMMsgDict::hasFolderIds( const FolderStorage& storage )
651{
652 return storage.rDict() != 0;
653}
654
655//-----------------------------------------------------------------------------
656
657bool KMMsgDict::removeFolderIds( FolderStorage& storage )
658{
659 storage.setRDict( 0 );
660 TQString filename = getFolderIdsLocation( storage );
661 return unlink( TQFile::encodeName( filename) );
662}
The FolderStorage class is the bass class for the storage related aspects of a collection of mail (a ...
Definition: folderstorage.h:80
virtual TQString indexLocation() const =0
Returns full path to index file.
KMMsgDictREntry * rDict() const
Returns the reverse-dictionary for this folder.
void setDirty(bool f)
Change the dirty flag.
TQString label() const
Returns the label of the folder for visualization.
void setRDict(KMMsgDictREntry *rentry) const
Sets the reverse-dictionary for this folder.
Class representing items in a KMDict.
Definition: kmdict.h:12
KMDict implements a lightweight dictionary with serial numbers as keys.
Definition: kmdict.h:27
void remove(long key)
Removes an item.
Definition: kmdict.cpp:76
KMDictItem * find(long key)
Find an item by key.
Definition: kmdict.cpp:107
void insert(long key, KMDictItem *item)
Inserts an item without replacing ones with the same key.
Definition: kmdict.cpp:66
A FolderStorage with an index for faster access to often used message properties.
Definition: kmfolderindex.h:38
virtual int find(const KMMsgBase *msg) const
Returns the index of the given message or -1 if not found.
Definition: kmfolderindex.h:71
Mail folder.
Definition: kmfolder.h:69
This is a Mime Message.
Definition: kmmessage.h:68
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
KMail message dictionary.
Definition: kmmsgdict.h:53
unsigned long getMsgSerNum(KMFolder *folder, int index) const
Find the message serial number for the message located at index index in folder folder.
Definition: kmmsgdict.cpp:345
static TQValueList< unsigned long > serNumList(TQPtrList< KMMsgBase > msgList)
Convert a list of KMMsgBase pointers to a list of serial numbers.
Definition: kmmsgdict.cpp:359
void getLocation(unsigned long key, KMFolder **retFolder, int *retIndex) const
Returns the folder the message represented by the serial number key is in and the index in that folde...
Definition: kmmsgdict.cpp:319
static const KMMsgDict * instance()
Access the globally unique MessageDict.
Definition: kmmsgdict.cpp:167