kmail

kmfolderindex.cpp

00001 /* -*- mode: C++; c-file-style: "gnu" -*-
00002     This file is part of KMail, the KDE mail client.
00003     Copyright (c) 2000 Don Sanders <sanders@kde.org>
00004 
00005     KMail is free software; you can redistribute it and/or modify it
00006     under the terms of the GNU General Public License, version 2, as
00007     published by the Free Software Foundation.
00008 
00009     KMail is distributed in the hope that it will be useful, but
00010     WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00017 */
00018 
00019 #include "kmfolderindex.h"
00020 #include "kmfolder.h"
00021 #include <config.h>
00022 #include <qfileinfo.h>
00023 #include <qtimer.h>
00024 #include <kdebug.h>
00025 
00026 
00027 #define HAVE_MMAP //need to get this into autoconf FIXME  --Sam
00028 #include <unistd.h>
00029 #ifdef HAVE_MMAP
00030 #include <sys/mman.h>
00031 #endif
00032 
00033 // Current version of the table of contents (index) files
00034 #define INDEX_VERSION 1506
00035 
00036 #ifndef MAX_LINE
00037 #define MAX_LINE 4096
00038 #endif
00039 
00040 #ifndef INIT_MSGS
00041 #define INIT_MSGS 8
00042 #endif
00043 
00044 #include <errno.h>
00045 #include <assert.h>
00046 #include <utime.h>
00047 #include <fcntl.h>
00048 
00049 #ifdef HAVE_BYTESWAP_H
00050 #include <byteswap.h>
00051 #endif
00052 #include <kapplication.h>
00053 #include <kcursor.h>
00054 #include <kmessagebox.h>
00055 #include <klocale.h>
00056 #include "kmmsgdict.h"
00057 
00058 // We define functions as kmail_swap_NN so that we don't get compile errors
00059 // on platforms where bswap_NN happens to be a function instead of a define.
00060 
00061 /* Swap bytes in 32 bit value.  */
00062 #ifdef bswap_32
00063 #define kmail_swap_32(x) bswap_32(x)
00064 #else
00065 #define kmail_swap_32(x) \
00066      ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >>  8) |           \
00067       (((x) & 0x0000ff00) <<  8) | (((x) & 0x000000ff) << 24))
00068 #endif
00069 
00070 #include <stdlib.h>
00071 #include <sys/types.h>
00072 #include <sys/stat.h>
00073 #include <sys/file.h>
00074 
00075 KMFolderIndex::KMFolderIndex(KMFolder* folder, const char* name)
00076   : FolderStorage(folder, name), mMsgList(INIT_MSGS)
00077 {
00078     mIndexStream = 0;
00079     mIndexStreamPtr = 0;
00080     mIndexStreamPtrLength = 0;
00081     mIndexSwapByteOrder = false;
00082     mIndexSizeOfLong = sizeof(long);
00083     mIndexId = 0;
00084     mHeaderOffset   = 0;
00085 }
00086 
00087 
00088 KMFolderIndex::~KMFolderIndex()
00089 {
00090 }
00091 
00092 
00093 QString KMFolderIndex::indexLocation() const
00094 {
00095   QString sLocation(folder()->path());
00096 
00097   if ( !sLocation.isEmpty() ) {
00098     sLocation += '/';
00099     sLocation += '.';
00100   }
00101   sLocation += dotEscape(fileName());
00102   sLocation += ".index";
00103 
00104   return sLocation;
00105 }
00106 
00107 int KMFolderIndex::updateIndex()
00108 {
00109   if (!mAutoCreateIndex)
00110     return 0;
00111   bool dirty = mDirty;
00112   mDirtyTimer->stop();
00113   for ( unsigned int i = 0; !dirty && i < mMsgList.high(); i++ ) {
00114     if ( mMsgList.at(i) ) {
00115       if ( !mMsgList.at(i)->syncIndexString() ) {
00116         dirty = true;
00117       }
00118     }
00119   }
00120   if (!dirty) { // Update successful
00121       touchFolderIdsFile();
00122       return 0;
00123   }
00124   return writeIndex();
00125 }
00126 
00127 int KMFolderIndex::writeIndex( bool createEmptyIndex )
00128 {
00129   QString tempName;
00130   QString indexName;
00131   mode_t old_umask;
00132   int len;
00133   const uchar *buffer = 0;
00134 
00135   indexName = indexLocation();
00136   tempName = indexName + ".temp";
00137   unlink(QFile::encodeName(tempName));
00138 
00139   // We touch the folder, otherwise the index is regenerated, if KMail is
00140   // running, while the clock switches from daylight savings time to normal time
00141   utime(QFile::encodeName(location()), 0);
00142 
00143   old_umask = umask(077);
00144   FILE *tmpIndexStream = fopen(QFile::encodeName(tempName), "w");
00145   umask(old_umask);
00146   if (!tmpIndexStream)
00147     return errno;
00148 
00149   fprintf(tmpIndexStream, "# KMail-Index V%d\n", INDEX_VERSION);
00150 
00151   // Header
00152   Q_UINT32 byteOrder = 0x12345678;
00153   Q_UINT32 sizeOfLong = sizeof(long);
00154 
00155   Q_UINT32 header_length = sizeof(byteOrder)+sizeof(sizeOfLong);
00156   char pad_char = '\0';
00157   fwrite(&pad_char, sizeof(pad_char), 1, tmpIndexStream);
00158   fwrite(&header_length, sizeof(header_length), 1, tmpIndexStream);
00159 
00160   // Write header
00161   fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream);
00162   fwrite(&sizeOfLong, sizeof(sizeOfLong), 1, tmpIndexStream);
00163 
00164   off_t nho = ftell(tmpIndexStream);
00165 
00166   if ( !createEmptyIndex ) {
00167     KMMsgBase* msgBase;
00168     for (unsigned int i=0; i<mMsgList.high(); i++)
00169     {
00170       if (!(msgBase = mMsgList.at(i))) continue;
00171       buffer = msgBase->asIndexString(len);
00172       fwrite(&len,sizeof(len), 1, tmpIndexStream);
00173 
00174       off_t tmp = ftell(tmpIndexStream);
00175       msgBase->setIndexOffset(tmp);
00176       msgBase->setIndexLength(len);
00177       if(fwrite(buffer, len, 1, tmpIndexStream) != 1)
00178     kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
00179     }
00180   }
00181 
00182   int fError = ferror( tmpIndexStream );
00183   if( fError != 0 ) {
00184     fclose( tmpIndexStream );
00185     return fError;
00186   }
00187   if(    ( fflush( tmpIndexStream ) != 0 )
00188       || ( fsync( fileno( tmpIndexStream ) ) != 0 ) ) {
00189     int errNo = errno;
00190     fclose( tmpIndexStream );
00191     return errNo;
00192   }
00193   if( fclose( tmpIndexStream ) != 0 )
00194     return errno;
00195 
00196   ::rename(QFile::encodeName(tempName), QFile::encodeName(indexName));
00197   mHeaderOffset = nho;
00198   if (mIndexStream)
00199       fclose(mIndexStream);
00200 
00201   if ( createEmptyIndex )
00202     return 0;
00203 
00204   mIndexStream = fopen(QFile::encodeName(indexName), "r+"); // index file
00205   assert( mIndexStream );
00206   fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00207 
00208   updateIndexStreamPtr();
00209 
00210   writeFolderIdsFile();
00211 
00212   setDirty( false );
00213   return 0;
00214 }
00215 
00216 
00217 bool KMFolderIndex::readIndex()
00218 {
00219   Q_INT32 len;
00220   KMMsgInfo* mi;
00221 
00222   assert(mIndexStream != 0);
00223   rewind(mIndexStream);
00224 
00225   clearIndex();
00226   int version;
00227 
00228   setDirty( false );
00229 
00230   if (!readIndexHeader(&version)) return false;
00231 
00232   mUnreadMsgs = 0;
00233   mTotalMsgs = 0;
00234   mHeaderOffset = ftell(mIndexStream);
00235 
00236   clearIndex();
00237   while (!feof(mIndexStream))
00238   {
00239     mi = 0;
00240     if(version >= 1505) {
00241       if(!fread(&len, sizeof(len), 1, mIndexStream))
00242         break;
00243 
00244       if (mIndexSwapByteOrder)
00245         len = kmail_swap_32(len);
00246 
00247       off_t offs = ftell(mIndexStream);
00248       if(fseek(mIndexStream, len, SEEK_CUR))
00249         break;
00250       mi = new KMMsgInfo(folder(), offs, len);
00251     }
00252     else
00253     {
00254       QCString line(MAX_LINE);
00255       fgets(line.data(), MAX_LINE, mIndexStream);
00256       if (feof(mIndexStream)) break;
00257       if (*line.data() == '\0') {
00258       fclose(mIndexStream);
00259       mIndexStream = 0;
00260       clearIndex();
00261       return false;
00262       }
00263       mi = new KMMsgInfo(folder());
00264       mi->compat_fromOldIndexString(line, mConvertToUtf8);
00265     }
00266     if(!mi)
00267       break;
00268 
00269     if (mi->isDeleted())
00270     {
00271       delete mi;  // skip messages that are marked as deleted
00272       setDirty( true );
00273       needsCompact = true;  //We have deleted messages - needs to be compacted
00274       continue;
00275     }
00276 #ifdef OBSOLETE
00277     else if (mi->isNew())
00278     {
00279       mi->setStatus(KMMsgStatusUnread);
00280       mi->setDirty(false);
00281     }
00282 #endif
00283     if ((mi->isNew()) || (mi->isUnread()) ||
00284         (folder() == kmkernel->outboxFolder()))
00285     {
00286       ++mUnreadMsgs;
00287       if (mUnreadMsgs == 0) ++mUnreadMsgs;
00288     }
00289     mMsgList.append(mi, false);
00290   }
00291   if( version < 1505)
00292   {
00293     mConvertToUtf8 = false;
00294     setDirty( true );
00295     writeIndex();
00296   }
00297   mTotalMsgs = mMsgList.count();
00298   return true;
00299 }
00300 
00301 
00302 int KMFolderIndex::count(bool cache) const
00303 {
00304   int res = FolderStorage::count(cache);
00305   if (res == -1)
00306     res = mMsgList.count();
00307   return res;
00308 }
00309 
00310 
00311 bool KMFolderIndex::readIndexHeader(int *gv)
00312 {
00313   int indexVersion;
00314   assert(mIndexStream != 0);
00315   mIndexSwapByteOrder = false;
00316   mIndexSizeOfLong = sizeof(long);
00317 
00318   int ret = fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion);
00319   if ( ret == EOF || ret == 0 )
00320       return false; // index file has invalid header
00321   if(gv)
00322       *gv = indexVersion;
00323   if (indexVersion < 1505 ) {
00324       if(indexVersion == 1503) {
00325       kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl;
00326       mConvertToUtf8 = true;
00327       }
00328       return true;
00329   } else if (indexVersion == 1505) {
00330   } else if (indexVersion < INDEX_VERSION) {
00331       kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl;
00332       createIndexFromContents();
00333       return false;
00334   } else if(indexVersion > INDEX_VERSION) {
00335       kapp->setOverrideCursor(KCursor::arrowCursor());
00336       int r = KMessageBox::questionYesNo(0,
00337                      i18n(
00338                         "The mail index for '%1' is from an unknown version of KMail (%2).\n"
00339                         "This index can be regenerated from your mail folder, but some "
00340                         "information, including status flags, may be lost. Do you wish "
00341                         "to downgrade your index file?") .arg(name()) .arg(indexVersion), QString::null, i18n("Downgrade"), i18n("Do Not Downgrade") );
00342       kapp->restoreOverrideCursor();
00343       if (r == KMessageBox::Yes)
00344       createIndexFromContents();
00345       return false;
00346   }
00347   else {
00348       // Header
00349       Q_UINT32 byteOrder = 0;
00350       Q_UINT32 sizeOfLong = sizeof(long); // default
00351 
00352       Q_UINT32 header_length = 0;
00353       fseek(mIndexStream, sizeof(char), SEEK_CUR );
00354       fread(&header_length, sizeof(header_length), 1, mIndexStream);
00355       if (header_length > 0xFFFF)
00356          header_length = kmail_swap_32(header_length);
00357 
00358       off_t endOfHeader = ftell(mIndexStream) + header_length;
00359 
00360       bool needs_update = true;
00361       // Process available header parts
00362       if (header_length >= sizeof(byteOrder))
00363       {
00364          fread(&byteOrder, sizeof(byteOrder), 1, mIndexStream);
00365          mIndexSwapByteOrder = (byteOrder == 0x78563412);
00366          header_length -= sizeof(byteOrder);
00367 
00368          if (header_length >= sizeof(sizeOfLong))
00369          {
00370             fread(&sizeOfLong, sizeof(sizeOfLong), 1, mIndexStream);
00371             if (mIndexSwapByteOrder)
00372                sizeOfLong = kmail_swap_32(sizeOfLong);
00373             mIndexSizeOfLong = sizeOfLong;
00374             header_length -= sizeof(sizeOfLong);
00375             needs_update = false;
00376          }
00377       }
00378       if (needs_update || mIndexSwapByteOrder || (mIndexSizeOfLong != sizeof(long)))
00379     setDirty( true );
00380       // Seek to end of header
00381       fseek(mIndexStream, endOfHeader, SEEK_SET );
00382 
00383       if (mIndexSwapByteOrder)
00384          kdDebug(5006) << "Index File has byte order swapped!" << endl;
00385       if (mIndexSizeOfLong != sizeof(long))
00386          kdDebug(5006) << "Index File sizeOfLong is " << mIndexSizeOfLong << " while sizeof(long) is " << sizeof(long) << " !" << endl;
00387 
00388   }
00389   return true;
00390 }
00391 
00392 
00393 #ifdef HAVE_MMAP
00394 bool KMFolderIndex::updateIndexStreamPtr(bool just_close)
00395 #else
00396 bool KMFolderIndex::updateIndexStreamPtr(bool)
00397 #endif
00398 {
00399     // We touch the folder, otherwise the index is regenerated, if KMail is
00400     // running, while the clock switches from daylight savings time to normal time
00401     utime(QFile::encodeName(location()), 0);
00402     utime(QFile::encodeName(indexLocation()), 0);
00403     utime(QFile::encodeName( KMMsgDict::getFolderIdsLocation( *this ) ), 0);
00404 
00405   mIndexSwapByteOrder = false;
00406 #ifdef HAVE_MMAP
00407     if(just_close) {
00408     if(mIndexStreamPtr)
00409         munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00410     mIndexStreamPtr = 0;
00411     mIndexStreamPtrLength = 0;
00412     return true;
00413     }
00414 
00415     assert(mIndexStream);
00416     struct stat stat_buf;
00417     if(fstat(fileno(mIndexStream), &stat_buf) == -1) {
00418     if(mIndexStreamPtr)
00419         munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00420     mIndexStreamPtr = 0;
00421     mIndexStreamPtrLength = 0;
00422     return false;
00423     }
00424     if(mIndexStreamPtr)
00425     munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00426     mIndexStreamPtrLength = stat_buf.st_size;
00427     mIndexStreamPtr = (uchar *)mmap(0, mIndexStreamPtrLength, PROT_READ, MAP_SHARED,
00428                     fileno(mIndexStream), 0);
00429     if(mIndexStreamPtr == MAP_FAILED) {
00430     mIndexStreamPtr = 0;
00431     mIndexStreamPtrLength = 0;
00432     return false;
00433     }
00434 #endif
00435     return true;
00436 }
00437 
00438 
00439 KMFolderIndex::IndexStatus KMFolderIndex::indexStatus()
00440 {
00441     QFileInfo contInfo(location());
00442     QFileInfo indInfo(indexLocation());
00443 
00444     if (!contInfo.exists()) return KMFolderIndex::IndexOk;
00445     if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
00446 
00447     return ( contInfo.lastModified() > indInfo.lastModified() )
00448         ? KMFolderIndex::IndexTooOld
00449         : KMFolderIndex::IndexOk;
00450 }
00451 
00452 void KMFolderIndex::clearIndex(bool autoDelete, bool syncDict)
00453 {
00454     mMsgList.clear(autoDelete, syncDict);
00455 }
00456 
00457 
00458 void KMFolderIndex::truncateIndex()
00459 {
00460   if ( mHeaderOffset )
00461     truncate(QFile::encodeName(indexLocation()), mHeaderOffset);
00462   else
00463     // The index file wasn't opened, so we don't know the header offset.
00464     // So let's just create a new empty index.
00465     writeIndex( true );
00466 }
00467 
00468 void KMFolderIndex::fillMessageDict()
00469 {
00470   open("fillDict");
00471   for (unsigned int idx = 0; idx < mMsgList.high(); idx++)
00472     if ( mMsgList.at( idx ) )
00473       KMMsgDict::mutableInstance()->insert(0, mMsgList.at( idx ), idx);
00474   close("fillDict");
00475 }
00476 
00477 
00478 KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg )
00479 {
00480   KMMsgInfo *msgInfo = msg->msgInfo();
00481   if ( !msgInfo )
00482     msgInfo = new KMMsgInfo( folder() );
00483 
00484   *msgInfo = *msg;
00485   mMsgList.set( idx, msgInfo );
00486   msg->setMsgInfo( 0 );
00487   delete msg;
00488   return msgInfo;
00489 }
00490 
00491 void KMFolderIndex::recreateIndex()
00492 {
00493   kapp->setOverrideCursor(KCursor::arrowCursor());
00494   KMessageBox::error(0,
00495        i18n("The mail index for '%1' is corrupted and will be regenerated now, "
00496             "but some information, including status flags, will be lost.").arg(name()));
00497   kapp->restoreOverrideCursor();
00498   createIndexFromContents();
00499   readIndex();
00500 }
00501 
00502 
00503 #include "kmfolderindex.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys