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 
53 class KMMsgDictEntry : public KMDictItem
54 {
55 public:
56  KMMsgDictEntry(const KMFolder *aFolder, int aIndex)
57  : folder( aFolder ), index( aIndex )
58  {}
59 
60  const KMFolder *folder;
61  int index;
62 };
63 
71 class KMMsgDictREntry
72 {
73 public:
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 
135 public:
136  TQMemArray<KMMsgDictEntry *> array;
137  FILE *fp;
138  bool swapByteOrder;
139  off_t baseOffset;
140 };
141 
142 
143 static KStaticDeleter<KMMsgDict> msgDict_sd;
144 KMMsgDict * KMMsgDict::m_self = 0;
145 
146 //-----------------------------------------------------------------------------
147 
148 KMMsgDict::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 
160 KMMsgDict::~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 
175 KMMsgDict* KMMsgDict::mutableInstance()
176 {
177  if ( !m_self ) {
178  msgDict_sd.setObject( m_self, new KMMsgDict() );
179  }
180  return m_self;
181 }
182 
183 //-----------------------------------------------------------------------------
184 
185 unsigned long KMMsgDict::getNextMsgSerNum() {
186  unsigned long msn = nextMsgSerNum;
187  nextMsgSerNum++;
188  return msn;
189 }
190 
191 void KMMsgDict::deleteRentry(KMMsgDictREntry *entry)
192 {
193  delete entry;
194 }
195 
196 unsigned 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 
241 unsigned 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 
249 void 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 
279 void 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 
295 unsigned long KMMsgDict::remove(const KMMsgBase *msg)
296 {
297  unsigned long msn = msg->getMsgSerNum();
298  remove(msn);
299  return msn;
300 }
301 
302 //-----------------------------------------------------------------------------
303 
304 void 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 
319 void 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 
332 void KMMsgDict::getLocation(const KMMsgBase *msg,
333  KMFolder **retFolder, int *retIndex) const
334 {
335  getLocation(msg->getMsgSerNum(), retFolder, retIndex);
336 }
337 
338 void KMMsgDict::getLocation( const KMMessage * msg, KMFolder * *retFolder, int * retIndex ) const
339 {
340  getLocation( msg->toMsgBase().getMsgSerNum(), retFolder, retIndex );
341 }
342 
343 //-----------------------------------------------------------------------------
344 
345 unsigned 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
359 TQValueList<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 
372 TQString KMMsgDict::getFolderIdsLocation( const FolderStorage &storage )
373 {
374  return storage.indexLocation() + ".ids";
375 }
376 
377 //-----------------------------------------------------------------------------
378 
379 bool 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 
396 int 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 
492 KMMsgDictREntry *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 
546 int 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 
585 int 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 
598 int 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 
650 bool KMMsgDict::hasFolderIds( const FolderStorage& storage )
651 {
652  return storage.rDict() != 0;
653 }
654 
655 //-----------------------------------------------------------------------------
656 
657 bool 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.
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.
KMMsgDictREntry * rDict() const
Returns 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