kmail Library API Documentation

kmfoldermbox.cpp

00001 // -*- c-basic-offset: 2 -*-
00002 // kmfoldermbox.cpp
00003 // Author: Stefan Taferner <taferner@alpin.or.at>
00004 
00005 #include <config.h>
00006 #include <qfileinfo.h>
00007 #include <qregexp.h>
00008 
00009 #include "kmfoldermbox.h"
00010 #include "kmfoldermgr.h"
00011 #include "undostack.h"
00012 #include "kcursorsaver.h"
00013 
00014 #include <kdebug.h>
00015 #include <klocale.h>
00016 #include <kmessagebox.h>
00017 #include <knotifyclient.h>
00018 #include <kprocess.h>
00019 #include <kconfig.h>
00020 
00021 #include <stdio.h>
00022 #include <errno.h>
00023 #include <assert.h>
00024 #include <unistd.h>
00025 
00026 #ifdef HAVE_FCNTL_H
00027 #include <fcntl.h>
00028 #endif
00029 
00030 #include <stdlib.h>
00031 #include <sys/types.h>
00032 #include <sys/stat.h>
00033 #include <sys/file.h>
00034 
00035 #ifndef MAX_LINE
00036 #define MAX_LINE 4096
00037 #endif
00038 #ifndef INIT_MSGS
00039 #define INIT_MSGS 8
00040 #endif
00041 
00042 // Regular expression to find the line that seperates messages in a mail
00043 // folder:
00044 #define MSG_SEPERATOR_START "From "
00045 #define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9].*$"
00046 static short msgSepLen = strlen(MSG_SEPERATOR_START);
00047 
00048 
00049 //-----------------------------------------------------------------------------
00050 KMFolderMbox::KMFolderMbox(KMFolderDir* aParent, const QString& aName)
00051   : KMFolderIndex(aParent, aName)
00052 {
00053   mStream         = 0;
00054   mFilesLocked    = false;
00055   mLockType       = lock_none;
00056 }
00057 
00058 
00059 //-----------------------------------------------------------------------------
00060 KMFolderMbox::~KMFolderMbox()
00061 {
00062   if (mOpenCount>0) close(true);
00063   if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed(this);
00064 }
00065 
00066 //-----------------------------------------------------------------------------
00067 int KMFolderMbox::open()
00068 {
00069   int rc = 0;
00070 
00071   mOpenCount++;
00072   if (mOpenCount > 1) return 0;  // already open
00073 
00074   assert(!name().isEmpty());
00075 
00076   mFilesLocked = false;
00077   mStream = fopen(QFile::encodeName(location()), "r+"); // messages file
00078   if (!mStream)
00079   {
00080     KNotifyClient::event( 0, "warning",
00081     i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno)));
00082     kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl;
00083     mOpenCount = 0;
00084     return errno;
00085   }
00086 
00087   lock();
00088 
00089   if (!path().isEmpty())
00090   {
00091      KMFolderIndex::IndexStatus index_status = indexStatus();
00092      // test if index file exists and is up-to-date
00093      if (KMFolderIndex::IndexOk != index_status)
00094      {
00095        // only show a warning if the index file exists, otherwise it can be
00096        // silently regenerated
00097        if (KMFolderIndex::IndexTooOld == index_status) {
00098         QString msg = i18n("<qt><p>The index of folder '%2' seems "
00099                            "to be out of date. To prevent message "
00100                            "corruption the index will be "
00101                            "regenerated. As a result deleted "
00102                            "messages might reappear and status "
00103                            "flags might be lost.</p>"
00104                            "<p>Please read the corresponding entry "
00105                            "in the <a href=\"%1\">FAQ section of the manual "
00106                            "of KMail</a> for "
00107                            "information about how to prevent this "
00108                            "problem from happening again.</p></qt>")
00109                       .arg("help:/kmail/faq.html#faq-index-regeneration")
00110                       .arg(name());
00111         // When KMail is starting up we have to show a non-blocking message
00112         // box so that the initialization can continue. We don't show a
00113         // queued message box when KMail isn't starting up because queued
00114         // message boxes don't have a "Don't ask again" checkbox.
00115         if (kmkernel->startingUp())
00116         {
00117           KConfigGroup configGroup( KMKernel::config(), "Notification Messages" );
00118           bool showMessage =
00119             configGroup.readBoolEntry( "showIndexRegenerationMessage", true );
00120           if (showMessage)
00121             KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
00122                                            msg, i18n("Index Out of Date"),
00123                                            KMessageBox::AllowLink );
00124         }
00125         else
00126         {
00127             KCursorSaver idle(KBusyPtr::idle());
00128             KMessageBox::information( 0, msg, i18n("Index Out of Date"),
00129                                       "showIndexRegenerationMessage",
00130                                       KMessageBox::AllowLink );
00131         }
00132        }
00133        QString str;
00134        mIndexStream = 0;
00135        str = i18n("Folder `%1' changed. Recreating index.")
00136              .arg(name());
00137        emit statusMsg(str);
00138      } else {
00139        mIndexStream = fopen(QFile::encodeName(indexLocation()), "r+"); // index file
00140        updateIndexStreamPtr();
00141      }
00142 
00143      if (!mIndexStream)
00144        rc = createIndexFromContents();
00145      else
00146        if (!readIndex())
00147          rc = createIndexFromContents();
00148   }
00149   else
00150   {
00151     mAutoCreateIndex = false;
00152     rc = createIndexFromContents();
00153   }
00154 
00155   mChanged = false;
00156 
00157   return rc;
00158 }
00159 
00160 //----------------------------------------------------------------------------
00161 int KMFolderMbox::canAccess()
00162 {
00163   assert(!name().isEmpty());
00164 
00165   if (access(QFile::encodeName(location()), R_OK | W_OK) != 0) {
00166     kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl;
00167       return 1;
00168   }
00169   return 0;
00170 }
00171 
00172 //-----------------------------------------------------------------------------
00173 int KMFolderMbox::create(bool imap)
00174 {
00175   int rc;
00176   int old_umask;
00177 
00178   Q_UNUSED(imap);
00179 
00180   assert(!name().isEmpty());
00181   assert(mOpenCount == 0);
00182 
00183   kdDebug(5006) << "Creating folder " << name() << endl;
00184   if (access(QFile::encodeName(location()), F_OK) == 0) {
00185     kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl;
00186     kdDebug(5006) << "File:: " << endl;
00187     kdDebug(5006) << "Error " << endl;
00188     return EEXIST;
00189   }
00190 
00191   old_umask = umask(077);
00192   mStream = fopen(QFile::encodeName(location()), "w+"); //sven; open RW
00193   umask(old_umask);
00194 
00195   if (!mStream) return errno;
00196 
00197   if (!path().isEmpty())
00198   {
00199     old_umask = umask(077);
00200     mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW
00201         updateIndexStreamPtr(true);
00202     umask(old_umask);
00203 
00204     if (!mIndexStream) return errno;
00205   }
00206   else
00207   {
00208     mAutoCreateIndex = false;
00209   }
00210 
00211   mOpenCount++;
00212   mChanged = false;
00213 
00214   rc = writeIndex();
00215   if (!rc) lock();
00216   return rc;
00217 }
00218 
00219 
00220 //-----------------------------------------------------------------------------
00221 void KMFolderMbox::close(bool aForced)
00222 {
00223   if (mOpenCount <= 0 || !mStream) return;
00224   if (mOpenCount > 0) mOpenCount--;
00225   if (mOpenCount > 0 && !aForced) return;
00226   if ((this != kmkernel->inboxFolder()) && isSystemFolder() && !aForced)
00227   {
00228       mOpenCount = 1;
00229       return;
00230   }
00231 
00232   if (mAutoCreateIndex)
00233   {
00234       if (KMFolderIndex::IndexOk != indexStatus()) {
00235           kdDebug(5006) << "Critical error: " << location() <<
00236               " has been modified by an external application while KMail was running." << endl;
00237           //      exit(1); backed out due to broken nfs
00238       }
00239 
00240       updateIndex();
00241       writeConfig();
00242   }
00243 
00244   if (!noContent()) {
00245     if (mStream) unlock();
00246     mMsgList.clear(true);
00247 
00248     if (mStream) fclose(mStream);
00249     if (mIndexStream) {
00250       fclose(mIndexStream);
00251       updateIndexStreamPtr(true);
00252     }
00253   }
00254   mOpenCount   = 0;
00255   mStream      = 0;
00256   mIndexStream = 0;
00257   mFilesLocked = false;
00258   mUnreadMsgs  = -1;
00259 
00260   mMsgList.reset(INIT_MSGS);
00261 }
00262 
00263 //-----------------------------------------------------------------------------
00264 void KMFolderMbox::sync()
00265 {
00266   if (mOpenCount > 0)
00267     if (!mStream || fsync(fileno(mStream)) ||
00268         !mIndexStream || fsync(fileno(mIndexStream))) {
00269     kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").arg( indexLocation() ).arg(errno ? QString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug.")));
00270     }
00271 }
00272 
00273 //-----------------------------------------------------------------------------
00274 int KMFolderMbox::lock()
00275 {
00276   int rc;
00277   struct flock fl;
00278   fl.l_type=F_WRLCK;
00279   fl.l_whence=0;
00280   fl.l_start=0;
00281   fl.l_len=0;
00282   fl.l_pid=-1;
00283   QCString cmd_str;
00284   assert(mStream != 0);
00285   mFilesLocked = false;
00286 
00287   switch( mLockType )
00288   {
00289     case FCNTL:
00290       rc = fcntl(fileno(mStream), F_SETLKW, &fl);
00291 
00292       if (rc < 0)
00293       {
00294         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00295                   << strerror(errno) << " (" << errno << ")" << endl;
00296         return errno;
00297       }
00298 
00299       if (mIndexStream)
00300       {
00301         rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);
00302 
00303         if (rc < 0)
00304         {
00305           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00306                     << strerror(errno) << " (" << errno << ")" << endl;
00307           rc = errno;
00308           fl.l_type = F_UNLCK;
00309           rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);
00310           return rc;
00311         }
00312       }
00313       break;
00314 
00315     case procmail_lockfile:
00316       cmd_str = "lockfile -l20 -r5 ";
00317       if (!mProcmailLockFileName.isEmpty())
00318         cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName));
00319       else
00320         cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock"));
00321 
00322       rc = system( cmd_str.data() );
00323       if( rc != 0 )
00324       {
00325         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00326                   << strerror(rc) << " (" << rc << ")" << endl;
00327         return rc;
00328       }
00329       if( mIndexStream )
00330       {
00331         cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock"));
00332         rc = system( cmd_str.data() );
00333         if( rc != 0 )
00334         {
00335           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00336                     << strerror(rc) << " (" << rc << ")" << endl;
00337           return rc;
00338         }
00339       }
00340       break;
00341 
00342     case mutt_dotlock:
00343       cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(location()));
00344       rc = system( cmd_str.data() );
00345       if( rc != 0 )
00346       {
00347         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00348                   << strerror(rc) << " (" << rc << ")" << endl;
00349         return rc;
00350       }
00351       if( mIndexStream )
00352       {
00353         cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(indexLocation()));
00354         rc = system( cmd_str.data() );
00355         if( rc != 0 )
00356         {
00357           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00358                     << strerror(rc) << " (" << rc << ")" << endl;
00359           return rc;
00360         }
00361       }
00362       break;
00363 
00364     case mutt_dotlock_privileged:
00365       cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(location()));
00366       rc = system( cmd_str.data() );
00367       if( rc != 0 )
00368       {
00369         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00370                   << strerror(rc) << " (" << rc << ")" << endl;
00371         return rc;
00372       }
00373       if( mIndexStream )
00374       {
00375         cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(indexLocation()));
00376         rc = system( cmd_str.data() );
00377         if( rc != 0 )
00378         {
00379           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00380                     << strerror(rc) << " (" << rc << ")" << endl;
00381           return rc;
00382         }
00383       }
00384       break;
00385 
00386     case lock_none:
00387     default:
00388       break;
00389   }
00390 
00391 
00392   mFilesLocked = true;
00393   return 0;
00394 }
00395 
00396 //-------------------------------------------------------------
00397 FolderJob*
00398 KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
00399                            KMFolder *folder, QString, const AttachmentStrategy* ) const
00400 {
00401   MboxJob *job = new MboxJob( msg, jt, folder );
00402   job->setParent( this );
00403   return job;
00404 }
00405 
00406 //-------------------------------------------------------------
00407 FolderJob*
00408 KMFolderMbox::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
00409                            FolderJob::JobType jt, KMFolder *folder ) const
00410 {
00411   MboxJob *job = new MboxJob( msgList, sets, jt, folder );
00412   job->setParent( this );
00413   return job;
00414 }
00415 
00416 //-----------------------------------------------------------------------------
00417 int KMFolderMbox::unlock()
00418 {
00419   int rc;
00420   struct flock fl;
00421   fl.l_type=F_UNLCK;
00422   fl.l_whence=0;
00423   fl.l_start=0;
00424   fl.l_len=0;
00425   QCString cmd_str;
00426 
00427   assert(mStream != 0);
00428   mFilesLocked = false;
00429 
00430   switch( mLockType )
00431   {
00432     case FCNTL:
00433       if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl);
00434       fcntl(fileno(mStream), F_SETLK, &fl);
00435       rc = errno;
00436       break;
00437 
00438     case procmail_lockfile:
00439       cmd_str = "rm -f ";
00440       if (!mProcmailLockFileName.isEmpty())
00441         cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName));
00442       else
00443         cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock"));
00444 
00445       rc = system( cmd_str.data() );
00446       if( mIndexStream )
00447       {
00448         cmd_str = "rm -f " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock"));
00449         rc = system( cmd_str.data() );
00450       }
00451       break;
00452 
00453     case mutt_dotlock:
00454       cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(location()));
00455       rc = system( cmd_str.data() );
00456       if( mIndexStream )
00457       {
00458         cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(indexLocation()));
00459         rc = system( cmd_str.data() );
00460       }
00461       break;
00462 
00463     case mutt_dotlock_privileged:
00464       cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(location()));
00465       rc = system( cmd_str.data() );
00466       if( mIndexStream )
00467       {
00468         cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(indexLocation()));
00469         rc = system( cmd_str.data() );
00470       }
00471       break;
00472 
00473     case lock_none:
00474     default:
00475       rc = 0;
00476       break;
00477   }
00478 
00479   return rc;
00480 }
00481 
00482 
00483 //-----------------------------------------------------------------------------
00484 KMFolderIndex::IndexStatus KMFolderMbox::indexStatus()
00485 {
00486   QFileInfo contInfo(location());
00487   QFileInfo indInfo(indexLocation());
00488 
00489   if (!contInfo.exists()) return KMFolderIndex::IndexOk;
00490   if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
00491 
00492   // Check whether the mbox file is more than 5 seconds newer than the index
00493   // file. The 5 seconds are added to reduce the number of false alerts due
00494   // to slightly out of sync clocks of the NFS server and the local machine.
00495   return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) )
00496       ? KMFolderIndex::IndexTooOld
00497       : KMFolderIndex::IndexOk;
00498 }
00499 
00500 
00501 //-----------------------------------------------------------------------------
00502 int KMFolderMbox::createIndexFromContents()
00503 {
00504   char line[MAX_LINE];
00505   char status[8], xstatus[8];
00506   QCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0;
00507   QCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr;
00508   bool atEof = false;
00509   bool inHeader = true;
00510   KMMsgInfo* mi;
00511   QString msgStr;
00512   QRegExp regexp(MSG_SEPERATOR_REGEX);
00513   int i, num, numStatus;
00514   short needStatus;
00515 
00516   assert(mStream != 0);
00517   rewind(mStream);
00518 
00519   mMsgList.clear();
00520 
00521   num     = -1;
00522   numStatus= 11;
00523   off_t offs = 0;
00524   size_t size = 0;
00525   dateStr = "";
00526   fromStr = "";
00527   toStr = "";
00528   subjStr = "";
00529   *status = '\0';
00530   *xstatus = '\0';
00531   xmarkStr = "";
00532   replyToIdStr = "";
00533   replyToAuxIdStr = "";
00534   referencesStr = "";
00535   msgIdStr = "";
00536   needStatus = 3;
00537 
00538 
00539   while (!atEof)
00540   {
00541     off_t pos = ftell(mStream);
00542     if (!fgets(line, MAX_LINE, mStream)) atEof = true;
00543 
00544     if (atEof ||
00545         (strncmp(line,MSG_SEPERATOR_START, msgSepLen)==0 &&
00546          regexp.search(line) >= 0))
00547     {
00548       size = pos - offs;
00549       pos = ftell(mStream);
00550 
00551       if (num >= 0)
00552       {
00553         if (numStatus <= 0)
00554         {
00555           msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num);
00556           emit statusMsg(msgStr);
00557           numStatus = 10;
00558         }
00559 
00560         if (size > 0)
00561         {
00562           msgIdStr = msgIdStr.stripWhiteSpace();
00563           if( !msgIdStr.isEmpty() ) {
00564             int rightAngle;
00565             rightAngle = msgIdStr.find( '>' );
00566             if( rightAngle != -1 )
00567               msgIdStr.truncate( rightAngle + 1 );
00568           }
00569 
00570           replyToIdStr = replyToIdStr.stripWhiteSpace();
00571           if( !replyToIdStr.isEmpty() ) {
00572             int rightAngle;
00573             rightAngle = replyToIdStr.find( '>' );
00574             if( rightAngle != -1 )
00575               replyToIdStr.truncate( rightAngle + 1 );
00576           }
00577 
00578           referencesStr = referencesStr.stripWhiteSpace();
00579           if( !referencesStr.isEmpty() ) {
00580             int leftAngle, rightAngle;
00581             leftAngle = referencesStr.findRev( '<' );
00582             if( ( leftAngle != -1 )
00583                 && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
00584               // use the last reference, instead of missing In-Reply-To
00585               replyToIdStr = referencesStr.mid( leftAngle );
00586             }
00587 
00588             // find second last reference
00589             leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
00590             if( leftAngle != -1 )
00591               referencesStr = referencesStr.mid( leftAngle );
00592             rightAngle = referencesStr.findRev( '>' );
00593             if( rightAngle != -1 )
00594               referencesStr.truncate( rightAngle + 1 );
00595 
00596             // Store the second to last reference in the replyToAuxIdStr
00597             // It is a good candidate for threading the message below if the
00598             // message In-Reply-To points to is not kept in this folder,
00599             // but e.g. in an Outbox
00600             replyToAuxIdStr = referencesStr;
00601             rightAngle = referencesStr.find( '>' );
00602             if( rightAngle != -1 )
00603               replyToAuxIdStr.truncate( rightAngle + 1 );
00604           }
00605 
00606           mi = new KMMsgInfo(this);
00607           mi->init( subjStr.stripWhiteSpace(),
00608                     fromStr.stripWhiteSpace(),
00609                     toStr.stripWhiteSpace(),
00610                     0, KMMsgStatusNew,
00611                     xmarkStr.stripWhiteSpace(),
00612                     replyToIdStr, replyToAuxIdStr, msgIdStr,
00613                     KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
00614                     KMMsgMDNStateUnknown, offs, size );
00615           mi->setStatus(status, xstatus);
00616           mi->setDate( dateStr.stripWhiteSpace() );
00617           mi->setDirty(false);
00618           mMsgList.append(mi);
00619 
00620           *status = '\0';
00621           *xstatus = '\0';
00622           needStatus = 3;
00623           xmarkStr = "";
00624           replyToIdStr = "";
00625           replyToAuxIdStr = "";
00626           referencesStr = "";
00627           msgIdStr = "";
00628           dateStr = "";
00629           fromStr = "";
00630           subjStr = "";
00631         }
00632         else num--,numStatus++;
00633       }
00634 
00635       offs = ftell(mStream);
00636       num++;
00637       numStatus--;
00638       inHeader = true;
00639       continue;
00640     }
00641     // Is this a long header line?
00642     if (inHeader && (line[0]=='\t' || line[0]==' '))
00643     {
00644       i = 0;
00645       while (line [i]=='\t' || line [i]==' ') i++;
00646       if (line [i] < ' ' && line [i]>0) inHeader = false;
00647       else if (lastStr) *lastStr += line + i;
00648     }
00649     else lastStr = 0;
00650 
00651     if (inHeader && (line [0]=='\n' || line [0]=='\r'))
00652       inHeader = false;
00653     if (!inHeader) continue;
00654 
00655     /* -sanders Make all messages read when auto-recreating index */
00656     /* Reverted, as it breaks reading the sent mail status, for example.
00657        -till */
00658     if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0)
00659     {
00660       for(i=0; i<4 && line[i+8] > ' '; i++)
00661         status[i] = line[i+8];
00662       status[i] = '\0';
00663       needStatus &= ~1;
00664     }
00665     else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0)
00666     {
00667       for(i=0; i<4 && line[i+10] > ' '; i++)
00668         xstatus[i] = line[i+10];
00669       xstatus[i] = '\0';
00670       needStatus &= ~2;
00671     }
00672     else if (strncasecmp(line,"X-KMail-Mark:",13)==0)
00673         xmarkStr = QCString(line+13);
00674     else if (strncasecmp(line,"In-Reply-To:",12)==0) {
00675       replyToIdStr = QCString(line+12);
00676       lastStr = &replyToIdStr;
00677     }
00678     else if (strncasecmp(line,"References:",11)==0) {
00679       referencesStr = QCString(line+11);
00680       lastStr = &referencesStr;
00681     }
00682     else if (strncasecmp(line,"Message-Id:",11)==0) {
00683       msgIdStr = QCString(line+11);
00684       lastStr = &msgIdStr;
00685     }
00686     else if (strncasecmp(line,"Date:",5)==0)
00687     {
00688       dateStr = QCString(line+5);
00689       lastStr = &dateStr;
00690     }
00691     else if (strncasecmp(line,"From:", 5)==0)
00692     {
00693       fromStr = QCString(line+5);
00694       lastStr = &fromStr;
00695     }
00696     else if (strncasecmp(line,"To:", 3)==0)
00697     {
00698       toStr = QCString(line+3);
00699       lastStr = &toStr;
00700     }
00701     else if (strncasecmp(line,"Subject:",8)==0)
00702     {
00703       subjStr = QCString(line+8);
00704       lastStr = &subjStr;
00705     }
00706   }
00707 
00708   if (mAutoCreateIndex)
00709   {
00710     emit statusMsg(i18n("Writing index file"));
00711     writeIndex();
00712   }
00713   else mHeaderOffset = 0;
00714 
00715   correctUnreadMsgsCount();
00716 
00717   if (kmkernel->outboxFolder() == this && count() > 0)
00718     KMessageBox::queuedMessageBox(0, KMessageBox::Information,
00719                                   i18n("Your outbox contains messages which were "
00720     "most likely not created by KMail.\nPlease remove them from there, if you "
00721     "don't want KMail to send them."));
00722 
00723   if ( parent() )
00724       parent()->manager()->invalidateFolder(kmkernel->msgDict(), this);
00725   return 0;
00726 }
00727 
00728 
00729 //-----------------------------------------------------------------------------
00730 KMMessage* KMFolderMbox::readMsg(int idx)
00731 {
00732   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00733 
00734   assert(mi!=0 && !mi->isMessage());
00735   assert(mStream != 0);
00736 
00737   KMMessage* msg = new KMMessage(*mi);
00738   msg->fromDwString( getDwString( idx ) );
00739   mMsgList.set(idx,&msg->toMsgBase());
00740 
00741   return msg;
00742 }
00743 
00744 
00745 #define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
00746 // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
00747 static size_t unescapeFrom( char* str, size_t strLen ) {
00748   if ( !str )
00749     return 0;
00750   if ( strLen <= STRDIM(">From ") )
00751     return strLen;
00752 
00753   // yes, *d++ = *s++ is a no-op as long as d == s (until after the
00754   // first >From_), but writes are cheap compared to reads and the
00755   // data is already in the cache from the read, so special-casing
00756   // might even be slower...
00757   const char * s = str;
00758   char * d = str;
00759   const char * const e = str + strLen - STRDIM(">From ");
00760 
00761   while ( s < e ) {
00762     if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end!
00763       *d++ = *s++;  // == '\n'
00764       *d++ = *s++;  // == '>'
00765       while ( s < e && *s == '>' )
00766         *d++ = *s++;
00767       if ( qstrncmp( s, "From ", STRDIM("From ") ) == 0 )
00768         --d;
00769     }
00770     *d++ = *s++; // yes, s might be e here, but e is not the end :-)
00771   }
00772   // copy the rest:
00773   while ( s < str + strLen )
00774     *d++ = *s++;
00775   if ( d < s ) // only NUL-terminate if it's shorter
00776     *d = 0;
00777 
00778   return d - str;
00779 }
00780 
00781 //static
00782 QCString KMFolderMbox::escapeFrom( const QCString & str ) {
00783   const unsigned int strLen = str.length();
00784   if ( strLen <= STRDIM("From ") )
00785     return str;
00786   // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
00787   QCString result( int( strLen + 5 ) / 6 * 7 + 1 );
00788 
00789   const char * s = str.data();
00790   const char * const e = s + strLen - STRDIM("From ");
00791   char * d = result.data();
00792 
00793   bool onlyAnglesAfterLF = false; // dont' match ^From_
00794   while ( s < e ) {
00795     switch ( *s ) {
00796     case '\n':
00797       onlyAnglesAfterLF = true;
00798       break;
00799     case '>':
00800       break;
00801     case 'F':
00802       if ( onlyAnglesAfterLF && qstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 )
00803         *d++ = '>';
00804       // fall through
00805     default:
00806       onlyAnglesAfterLF = false;
00807       break;
00808     }
00809     *d++ = *s++;
00810   }
00811   while ( s < str.data() + strLen )
00812     *d++ = *s++;
00813 
00814   result.truncate( d - result.data() );
00815   return result;
00816 }
00817 
00818 #undef STRDIM
00819 
00820 //-----------------------------------------------------------------------------
00821 QCString& KMFolderMbox::getMsgString(int idx, QCString &mDest)
00822 {
00823   unsigned long msgSize;
00824   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00825 
00826   assert(mi!=0);
00827   assert(mStream != 0);
00828 
00829   msgSize = mi->msgSize();
00830   mDest.resize(msgSize+2);
00831 
00832   fseek(mStream, mi->folderOffset(), SEEK_SET);
00833   fread(mDest.data(), msgSize, 1, mStream);
00834   mDest[msgSize] = '\0';
00835 
00836   size_t newMsgSize = unescapeFrom( mDest.data(), msgSize );
00837   newMsgSize = crlf2lf( mDest.data(), newMsgSize );
00838 
00839   return mDest;
00840 }
00841 
00842 
00843 //-----------------------------------------------------------------------------
00844 DwString KMFolderMbox::getDwString(int idx)
00845 {
00846   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00847 
00848   assert(mi!=0);
00849   assert(mStream != 0);
00850 
00851   size_t msgSize = mi->msgSize();
00852   char* msgText = new char[ msgSize + 1 ];
00853 
00854   fseek(mStream, mi->folderOffset(), SEEK_SET);
00855   fread(msgText, msgSize, 1, mStream);
00856   msgText[msgSize] = '\0';
00857 
00858   size_t newMsgSize = unescapeFrom( msgText, msgSize );
00859   newMsgSize = crlf2lf( msgText, newMsgSize );
00860 
00861   DwString msgStr;
00862   // the DwString takes possession of msgText, so we must not delete msgText
00863   msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
00864   return msgStr;
00865 }
00866 
00867 
00868 //-----------------------------------------------------------------------------
00869 int KMFolderMbox::addMsg(KMMessage* aMsg, int* aIndex_ret)
00870 {
00871   if (!canAddMsgNow(aMsg, aIndex_ret)) return 0;
00872   bool opened = false;
00873   QCString msgText;
00874   char endStr[3];
00875   int idx = -1, rc;
00876   KMFolder* msgParent;
00877   bool editing = false;
00878   int growth = 0;
00879 
00880 /* Then we can also disable it completely, this wastes time, at least for IMAP
00881   if (KMFolder::IndexOk != indexStatus()) {
00882       kdDebug(5006) << "Critical error: " << location() <<
00883           " has been modified by an external application while KMail was running." << endl;
00884       //      exit(1); backed out due to broken nfs
00885   } */
00886 
00887   if (!mStream)
00888   {
00889     opened = true;
00890     rc = open();
00891     kdDebug(5006) << "addMsg-open: " << rc << endl;
00892     if (rc) return rc;
00893   }
00894 
00895   // take message out of the folder it is currently in, if any
00896   msgParent = aMsg->parent();
00897   if (msgParent)
00898   {
00899     if (msgParent==this)
00900     {
00901         if (kmkernel->folderIsDraftOrOutbox(this))
00902           //special case for Edit message.
00903           {
00904             kdDebug(5006) << "Editing message in outbox or drafts" << endl;
00905             editing = true;
00906           }
00907         else
00908           return 0;
00909       }
00910 
00911     idx = msgParent->find(aMsg);
00912     msgParent->getMsg( idx );
00913   }
00914 
00915   if (folderType() != KMFolderTypeImap)
00916   {
00917 /*
00918 QFile fileD0( "testdat_xx-kmfoldermbox-0" );
00919 if( fileD0.open( IO_WriteOnly ) ) {
00920     QDataStream ds( &fileD0 );
00921     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00922     fileD0.close();  // If data is 0 we just create a zero length file.
00923 }
00924 */
00925     aMsg->setStatusFields();
00926 /*
00927 QFile fileD1( "testdat_xx-kmfoldermbox-1" );
00928 if( fileD1.open( IO_WriteOnly ) ) {
00929     QDataStream ds( &fileD1 );
00930     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00931     fileD1.close();  // If data is 0 we just create a zero length file.
00932 }
00933 */
00934     if (aMsg->headerField("Content-Type").isEmpty())  // This might be added by
00935       aMsg->removeHeaderField("Content-Type");        // the line above
00936   }
00937   msgText = escapeFrom( aMsg->asString() );
00938   size_t len = msgText.length();
00939 
00940   assert(mStream != 0);
00941   clearerr(mStream);
00942   if (len <= 0)
00943   {
00944     kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
00945     if (opened) close();
00946     return 0;
00947   }
00948 
00949   // Make sure the file is large enough to check for an end
00950   // character
00951   fseek(mStream, 0, SEEK_END);
00952   off_t revert = ftell(mStream);
00953   if (ftell(mStream) >= 2) {
00954       // write message to folder file
00955       fseek(mStream, -2, SEEK_END);
00956       fread(endStr, 1, 2, mStream); // ensure separating empty line
00957       if (ftell(mStream) > 0 && endStr[0]!='\n') {
00958           ++growth;
00959           if (endStr[1]!='\n') {
00960               //printf ("****endStr[1]=%c\n", endStr[1]);
00961               fwrite("\n\n", 1, 2, mStream);
00962               ++growth;
00963           }
00964           else fwrite("\n", 1, 1, mStream);
00965       }
00966   }
00967   fseek(mStream,0,SEEK_END); // this is needed on solaris and others
00968   int error = ferror(mStream);
00969   if (error)
00970   {
00971     if (opened) close();
00972     return error;
00973   }
00974 
00975   QCString address( aMsg->fromEmail() );
00976   if ( address.isEmpty() )
00977     address = "unknown@unknown.invalid";
00978   fprintf(mStream, "From %s %s\n", address.data(),
00979           (const char *)aMsg->dateShortStr());
00980   off_t offs = ftell(mStream);
00981   fwrite(msgText, len, 1, mStream);
00982   if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream);
00983   fflush(mStream);
00984   size_t size = ftell(mStream) - offs;
00985 
00986   error = ferror(mStream);
00987   if (error) {
00988     kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl;
00989     if (ftell(mStream) > revert) {
00990       kdDebug(5006) << "Undoing changes" << endl;
00991       truncate( QFile::encodeName(location()), revert );
00992     }
00993     kmkernel->emergencyExit( i18n("Could not add message to folder: ") + QString::fromLocal8Bit(strerror(errno)));
00994 
00995     /* This code is not 100% reliable
00996     bool busy = kmkernel->kbp()->isBusy();
00997     if (busy) kmkernel->kbp()->idle();
00998     KMessageBox::sorry(0,
00999           i18n("Unable to add message to folder.\n"
01000                "(No space left on device or insufficient quota?)\n"
01001                "Free space and sufficient quota are required to continue safely."));
01002     if (busy) kmkernel->kbp()->busy();
01003     if (opened) close();
01004     kmkernel->kbp()->idle();
01005     */
01006     return error;
01007   }
01008 
01009   if (msgParent) {
01010     if (idx >= 0) msgParent->take(idx);
01011   }
01012 //  if (mAccount) aMsg->removeHeaderField("X-UID");
01013 
01014   if (aMsg->isUnread() || aMsg->isNew() ||
01015       (this == kmkernel->outboxFolder())) {
01016     if (mUnreadMsgs == -1) mUnreadMsgs = 1;
01017     else ++mUnreadMsgs;
01018     emit numUnreadMsgsChanged( this );
01019   }
01020   ++mTotalMsgs;
01021 
01022   // store information about the position in the folder file in the message
01023   aMsg->setParent(this);
01024   aMsg->setFolderOffset(offs);
01025   aMsg->setMsgSize(size);
01026   idx = mMsgList.append(&aMsg->toMsgBase());
01027   if (aMsg->getMsgSerNum() <= 0)
01028     aMsg->setMsgSerNum();
01029 
01030   // change the length of the previous message to encompass white space added
01031   if ((idx > 0) && (growth > 0)) {
01032     // don't grow if a deleted message claims space at the end of the file
01033     if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() )
01034       mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth );
01035   }
01036 
01037   // write index entry if desired
01038   if (mAutoCreateIndex)
01039   {
01040     assert(mIndexStream != 0);
01041     clearerr(mIndexStream);
01042     fseek(mIndexStream, 0, SEEK_END);
01043     revert = ftell(mIndexStream);
01044 
01045     KMMsgBase * mb = &aMsg->toMsgBase();
01046         int len;
01047         const uchar *buffer = mb->asIndexString(len);
01048         fwrite(&len,sizeof(len), 1, mIndexStream);
01049         mb->setIndexOffset( ftell(mIndexStream) );
01050         mb->setIndexLength( len );
01051         if(fwrite(buffer, len, 1, mIndexStream) != 1)
01052             kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
01053 
01054     fflush(mIndexStream);
01055     error = ferror(mIndexStream);
01056 
01057     error |= appendtoMsgDict(idx);
01058 
01059     if (error) {
01060       kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
01061       if (ftell(mIndexStream) > revert) {
01062         kdWarning(5006) << "Undoing changes" << endl;
01063         truncate( QFile::encodeName(indexLocation()), revert );
01064       }
01065       if ( errno )
01066         kmkernel->emergencyExit( i18n("Could not add message to folder:") + QString::fromLocal8Bit(strerror(errno)));
01067       else
01068         kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") );
01069 
01070       /* This code may not be 100% reliable
01071       bool busy = kmkernel->kbp()->isBusy();
01072       if (busy) kmkernel->kbp()->idle();
01073       KMessageBox::sorry(0,
01074         i18n("Unable to add message to folder.\n"
01075              "(No space left on device or insufficient quota?)\n"
01076              "Free space and sufficient quota are required to continue safely."));
01077       if (busy) kmkernel->kbp()->busy();
01078       if (opened) close();
01079       */
01080       return error;
01081     }
01082   }
01083 
01084   // some "paper work"
01085   if (aIndex_ret) *aIndex_ret = idx;
01086   emitMsgAddedSignals(idx);
01087   if (opened) close();
01088 
01089   // All streams have been flushed without errors if we arrive here
01090   // Return success!
01091   // (Don't return status of stream, it may have been closed already.)
01092   return 0;
01093 }
01094 
01095 
01096 //-----------------------------------------------------------------------------
01097 int KMFolderMbox::compact()
01098 {
01099   QString tempName;
01100   QString msgStr;
01101   int rc = 0;
01102   int openCount = mOpenCount;
01103 
01104   if (!needsCompact)
01105     return 0;
01106 
01107   if (!mCompactable) {
01108     kdDebug(5006) << location() << " compaction skipped." << endl;
01109     return 0;
01110   }
01111   kdDebug(5006) << "Compacting " << idString() << endl;
01112 
01113   if (KMFolderIndex::IndexOk != indexStatus()) {
01114       kdDebug(5006) << "Critical error: " << location() <<
01115           " has been modified by an external application while KMail was running." << endl;
01116       //      exit(1); backed out due to broken nfs
01117   }
01118 
01119   tempName = path() + "/." + name() + ".compacted";
01120   mode_t old_umask = umask(077);
01121   FILE *tmpfile = fopen(QFile::encodeName(tempName), "w");
01122   umask(old_umask);
01123   if (!tmpfile)
01124     return errno;
01125   open();
01126 
01127   KMMsgInfo* mi;
01128   size_t msize;
01129   off_t folder_offset;
01130   off_t offs=0;
01131   int msgs=0;
01132   QCString mtext;
01133   for(unsigned int idx = 0; idx < mMsgList.count(); idx++) {
01134     if(!(msgs++ % 10)) {
01135       msgStr = i18n("Compacting folder: one message done",
01136                                 "Compacting folder: %n messages done", msgs);
01137       if (!kmkernel->shuttingDown())
01138           emit statusMsg(msgStr);
01139     }
01140     mi = (KMMsgInfo*)mMsgList.at(idx);
01141     msize = mi->msgSize();
01142     if (mtext.size() < msize + 2)
01143       mtext.resize(msize+2);
01144     folder_offset = mi->folderOffset();
01145 
01146     //now we need to find the separator! grr...
01147     for(off_t i = folder_offset-25; true; i -= 20) {
01148       off_t chunk_offset = i <= 0 ? 0 : i;
01149       if(fseek(mStream, chunk_offset, SEEK_SET) == -1) {
01150         rc = errno;
01151         break;
01152       }
01153       if (mtext.size() < 20)
01154         mtext.resize(20);
01155       fread(mtext.data(), 20, 1, mStream);
01156       if(i <= 0) { //woops we've reached the top of the file, last try..
01157         if ( mtext.contains( "from ", false ) ) {
01158           if (mtext.size() < (size_t)folder_offset)
01159               mtext.resize(folder_offset);
01160           if(fseek(mStream, chunk_offset, SEEK_SET) == -1 ||
01161              !fread(mtext.data(), folder_offset, 1, mStream) ||
01162              !fwrite(mtext.data(), folder_offset, 1, tmpfile)) {
01163               rc = errno;
01164               break;
01165           }
01166           offs += folder_offset;
01167         } else {
01168             rc = 666;
01169         }
01170         break;
01171       } else {
01172         int last_crlf = -1;
01173         for(int i2 = 0; i2 < 20; i2++) {
01174           if(*(mtext.data()+i2) == '\n')
01175             last_crlf = i2;
01176         }
01177         if(last_crlf != -1) {
01178           int size = folder_offset - (i + last_crlf+1);
01179           if ((int)mtext.size() < size)
01180               mtext.resize(size);
01181           if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 ||
01182              !fread(mtext.data(), size, 1, mStream) ||
01183              !fwrite(mtext.data(), size, 1, tmpfile)) {
01184               rc = errno;
01185               break;
01186           }
01187           offs += size;
01188           break;
01189         }
01190       }
01191     }
01192     if (rc)
01193       break;
01194 
01195     //now actually write the message
01196     if(fseek(mStream, folder_offset, SEEK_SET) == -1 ||
01197        !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) {
01198         rc = errno;
01199         break;
01200     }
01201     mi->setFolderOffset(offs);
01202     offs += msize;
01203   }
01204   if (!rc)
01205       rc = fflush(tmpfile);
01206   if (!rc)
01207       rc = fsync(fileno(tmpfile));
01208   rc |= fclose(tmpfile);
01209   if (!rc) {
01210     bool autoCreate = mAutoCreateIndex;
01211     QFileInfo inf(location());
01212     QString box;
01213     if (inf.isSymLink())
01214       box = inf.readLink();
01215     if (!box)
01216       box = location();
01217     ::rename(QFile::encodeName(tempName), QFile::encodeName(box));
01218     writeIndex();
01219     writeConfig();
01220     mAutoCreateIndex = false;
01221     close(true);
01222     mAutoCreateIndex = autoCreate;
01223     needsCompact = false;             // We are clean now
01224   }
01225   else
01226   {
01227     close();
01228     kdDebug(5006) << "Error occurred while compacting" << endl;
01229     kdDebug(5006) << location() << endl;
01230     kdDebug(5006) << "Compaction aborted." << endl;
01231   }
01232 
01233   if (openCount > 0)
01234   {
01235     open();
01236     mOpenCount = openCount;
01237   }
01238   emit changed();
01239   return 0;
01240 
01241 }
01242 
01243 
01244 //-----------------------------------------------------------------------------
01245 void KMFolderMbox::setLockType( LockType ltype )
01246 {
01247   mLockType = ltype;
01248 }
01249 
01250 //-----------------------------------------------------------------------------
01251 void KMFolderMbox::setProcmailLockFileName( const QString &fname )
01252 {
01253   mProcmailLockFileName = fname;
01254 }
01255 
01256 //-----------------------------------------------------------------------------
01257 int KMFolderMbox::removeContents()
01258 {
01259   int rc = 0;
01260   rc = unlink(QFile::encodeName(location()));
01261   return rc;
01262 }
01263 
01264 //-----------------------------------------------------------------------------
01265 int KMFolderMbox::expungeContents()
01266 {
01267   int rc = 0;
01268   if (truncate(QFile::encodeName(location()), 0))
01269     rc = errno;
01270   return rc;
01271 }
01272 
01273 //-----------------------------------------------------------------------------
01274 #include "kmfoldermbox.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 1 11:37:27 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003