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 1507
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   //kdDebug(5006) << "Index version for " << label() << " is " << version << endl;
00232 
00233   mUnreadMsgs = 0;
00234   mTotalMsgs = 0;
00235   mHeaderOffset = ftell(mIndexStream);
00236 
00237   clearIndex();
00238   while (!feof(mIndexStream))
00239   {
00240     mi = 0;
00241     if(version >= 1505) {
00242       if(!fread(&len, sizeof(len), 1, mIndexStream))
00243         break;
00244 
00245       if (mIndexSwapByteOrder)
00246         len = kmail_swap_32(len);
00247 
00248       off_t offs = ftell(mIndexStream);
00249       if(fseek(mIndexStream, len, SEEK_CUR))
00250         break;
00251       mi = new KMMsgInfo(folder(), offs, len);
00252     }
00253     else
00254     {
00255       QCString line(MAX_LINE);
00256       fgets(line.data(), MAX_LINE, mIndexStream);
00257       if (feof(mIndexStream)) break;
00258       if (*line.data() == '\0') {
00259       fclose(mIndexStream);
00260       mIndexStream = 0;
00261       clearIndex();
00262       return false;
00263       }
00264       mi = new KMMsgInfo(folder());
00265       mi->compat_fromOldIndexString(line, mConvertToUtf8);
00266     }
00267     if(!mi)
00268       break;
00269 
00270     if (mi->isDeleted())
00271     {
00272       delete mi;  // skip messages that are marked as deleted
00273       setDirty( true );
00274       needsCompact = true;  //We have deleted messages - needs to be compacted
00275       continue;
00276     }
00277 #ifdef OBSOLETE
00278     else if (mi->isNew())
00279     {
00280       mi->setStatus(KMMsgStatusUnread);
00281       mi->setDirty(false);
00282     }
00283 #endif
00284     if ((mi->isNew()) || (mi->isUnread()) ||
00285         (folder() == kmkernel->outboxFolder()))
00286     {
00287       ++mUnreadMsgs;
00288       if (mUnreadMsgs == 0) ++mUnreadMsgs;
00289     }
00290     mMsgList.append(mi, false);
00291   }
00292   if( version < 1505)
00293   {
00294     mConvertToUtf8 = false;
00295     setDirty( true );
00296     writeIndex();
00297   }
00298 
00299   if ( version < 1507 ) {
00300     updateInvitationAndAddressFieldsFromContents();
00301     setDirty( true );
00302     writeIndex();
00303   }
00304 
00305   mTotalMsgs = mMsgList.count();
00306   return true;
00307 }
00308 
00309 
00310 int KMFolderIndex::count(bool cache) const
00311 {
00312   int res = FolderStorage::count(cache);
00313   if (res == -1)
00314     res = mMsgList.count();
00315   return res;
00316 }
00317 
00318 
00319 bool KMFolderIndex::readIndexHeader(int *gv)
00320 {
00321   int indexVersion;
00322   assert(mIndexStream != 0);
00323   mIndexSwapByteOrder = false;
00324   mIndexSizeOfLong = sizeof(long);
00325 
00326   int ret = fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion);
00327   if ( ret == EOF || ret == 0 )
00328       return false; // index file has invalid header
00329   if(gv)
00330       *gv = indexVersion;
00331   if (indexVersion < 1505 ) {
00332       if(indexVersion == 1503) {
00333       kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl;
00334       mConvertToUtf8 = true;
00335       }
00336       return true;
00337   } else if (indexVersion == 1505) {
00338   } else if (indexVersion < INDEX_VERSION && indexVersion != 1506) {
00339       kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl;
00340       createIndexFromContents();
00341       return false;
00342   } else if(indexVersion > INDEX_VERSION) {
00343       kapp->setOverrideCursor(KCursor::arrowCursor());
00344       int r = KMessageBox::questionYesNo(0,
00345                      i18n(
00346                         "The mail index for '%1' is from an unknown version of KMail (%2).\n"
00347                         "This index can be regenerated from your mail folder, but some "
00348                         "information, including status flags, may be lost. Do you wish "
00349                         "to downgrade your index file?") .arg(name()) .arg(indexVersion), QString::null, i18n("Downgrade"), i18n("Do Not Downgrade") );
00350       kapp->restoreOverrideCursor();
00351       if (r == KMessageBox::Yes)
00352       createIndexFromContents();
00353       return false;
00354   }
00355   else {
00356       // Header
00357       Q_UINT32 byteOrder = 0;
00358       Q_UINT32 sizeOfLong = sizeof(long); // default
00359 
00360       Q_UINT32 header_length = 0;
00361       fseek(mIndexStream, sizeof(char), SEEK_CUR );
00362       fread(&header_length, sizeof(header_length), 1, mIndexStream);
00363       if (header_length > 0xFFFF)
00364          header_length = kmail_swap_32(header_length);
00365 
00366       off_t endOfHeader = ftell(mIndexStream) + header_length;
00367 
00368       bool needs_update = true;
00369       // Process available header parts
00370       if (header_length >= sizeof(byteOrder))
00371       {
00372          fread(&byteOrder, sizeof(byteOrder), 1, mIndexStream);
00373          mIndexSwapByteOrder = (byteOrder == 0x78563412);
00374          header_length -= sizeof(byteOrder);
00375 
00376          if (header_length >= sizeof(sizeOfLong))
00377          {
00378             fread(&sizeOfLong, sizeof(sizeOfLong), 1, mIndexStream);
00379             if (mIndexSwapByteOrder)
00380                sizeOfLong = kmail_swap_32(sizeOfLong);
00381             mIndexSizeOfLong = sizeOfLong;
00382             header_length -= sizeof(sizeOfLong);
00383             needs_update = false;
00384          }
00385       }
00386       if (needs_update || mIndexSwapByteOrder || (mIndexSizeOfLong != sizeof(long)))
00387     setDirty( true );
00388       // Seek to end of header
00389       fseek(mIndexStream, endOfHeader, SEEK_SET );
00390 
00391       if (mIndexSwapByteOrder)
00392          kdDebug(5006) << "Index File has byte order swapped!" << endl;
00393       if (mIndexSizeOfLong != sizeof(long))
00394          kdDebug(5006) << "Index File sizeOfLong is " << mIndexSizeOfLong << " while sizeof(long) is " << sizeof(long) << " !" << endl;
00395 
00396   }
00397   return true;
00398 }
00399 
00400 
00401 #ifdef HAVE_MMAP
00402 bool KMFolderIndex::updateIndexStreamPtr(bool just_close)
00403 #else
00404 bool KMFolderIndex::updateIndexStreamPtr(bool)
00405 #endif
00406 {
00407     // We touch the folder, otherwise the index is regenerated, if KMail is
00408     // running, while the clock switches from daylight savings time to normal time
00409     utime(QFile::encodeName(location()), 0);
00410     utime(QFile::encodeName(indexLocation()), 0);
00411     utime(QFile::encodeName( KMMsgDict::getFolderIdsLocation( *this ) ), 0);
00412 
00413   mIndexSwapByteOrder = false;
00414 #ifdef HAVE_MMAP
00415     if(just_close) {
00416     if(mIndexStreamPtr)
00417         munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00418     mIndexStreamPtr = 0;
00419     mIndexStreamPtrLength = 0;
00420     return true;
00421     }
00422 
00423     assert(mIndexStream);
00424     struct stat stat_buf;
00425     if(fstat(fileno(mIndexStream), &stat_buf) == -1) {
00426     if(mIndexStreamPtr)
00427         munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00428     mIndexStreamPtr = 0;
00429     mIndexStreamPtrLength = 0;
00430     return false;
00431     }
00432     if(mIndexStreamPtr)
00433     munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
00434     mIndexStreamPtrLength = stat_buf.st_size;
00435     mIndexStreamPtr = (uchar *)mmap(0, mIndexStreamPtrLength, PROT_READ, MAP_SHARED,
00436                     fileno(mIndexStream), 0);
00437     if(mIndexStreamPtr == MAP_FAILED) {
00438     mIndexStreamPtr = 0;
00439     mIndexStreamPtrLength = 0;
00440     return false;
00441     }
00442 #endif
00443     return true;
00444 }
00445 
00446 
00447 KMFolderIndex::IndexStatus KMFolderIndex::indexStatus()
00448 {
00449     QFileInfo contInfo(location());
00450     QFileInfo indInfo(indexLocation());
00451 
00452     if (!contInfo.exists()) return KMFolderIndex::IndexOk;
00453     if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
00454 
00455     return ( contInfo.lastModified() > indInfo.lastModified() )
00456         ? KMFolderIndex::IndexTooOld
00457         : KMFolderIndex::IndexOk;
00458 }
00459 
00460 void KMFolderIndex::clearIndex(bool autoDelete, bool syncDict)
00461 {
00462     mMsgList.clear(autoDelete, syncDict);
00463 }
00464 
00465 
00466 void KMFolderIndex::truncateIndex()
00467 {
00468   if ( mHeaderOffset )
00469     truncate(QFile::encodeName(indexLocation()), mHeaderOffset);
00470   else
00471     // The index file wasn't opened, so we don't know the header offset.
00472     // So let's just create a new empty index.
00473     writeIndex( true );
00474 }
00475 
00476 void KMFolderIndex::fillMessageDict()
00477 {
00478   open("fillDict");
00479   for (unsigned int idx = 0; idx < mMsgList.high(); idx++)
00480     if ( mMsgList.at( idx ) )
00481       KMMsgDict::mutableInstance()->insert(0, mMsgList.at( idx ), idx);
00482   close("fillDict");
00483 }
00484 
00485 
00486 KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg )
00487 {
00488   KMMsgInfo *msgInfo = msg->msgInfo();
00489   if ( !msgInfo )
00490     msgInfo = new KMMsgInfo( folder() );
00491 
00492   *msgInfo = *msg;
00493   mMsgList.set( idx, msgInfo );
00494   msg->setMsgInfo( 0 );
00495   delete msg;
00496   return msgInfo;
00497 }
00498 
00499 void KMFolderIndex::recreateIndex()
00500 {
00501   kapp->setOverrideCursor(KCursor::arrowCursor());
00502   KMessageBox::error(0,
00503        i18n("The mail index for '%1' is corrupted and will be regenerated now, "
00504             "but some information, including status flags, will be lost.").arg(name()));
00505   kapp->restoreOverrideCursor();
00506   createIndexFromContents();
00507   readIndex();
00508 }
00509 
00510 void KMFolderIndex::updateInvitationAndAddressFieldsFromContents()
00511 {
00512   kdDebug(5006) << "Updating index for " << label() << ", this might take a while." << endl;
00513   for ( uint i = 0; i < mMsgList.size(); i++ ) {
00514     KMMsgInfo * const msgInfo = dynamic_cast<KMMsgInfo*>( mMsgList[i] );
00515     if ( msgInfo ) {
00516       DwString msgString( getDwString( i ) );
00517       if ( msgString.size() > 0 ) {
00518         KMMessage msg;
00519         msg.fromDwString( msgString, false );
00520         msg.updateInvitationState();
00521         if ( msg.status() & KMMsgStatusHasInvitation ) {
00522           msgInfo->setStatus( msgInfo->status() | KMMsgStatusHasInvitation );
00523         }
00524         if ( msg.status() & KMMsgStatusHasNoInvitation ) {
00525           msgInfo->setStatus( msgInfo->status() | KMMsgStatusHasNoInvitation );
00526         }
00527         msgInfo->setFrom( msg.from() );
00528         msgInfo->setTo( msg.to() );
00529       }
00530     }
00531   }
00532 }
00533 
00534 #include "kmfolderindex.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys