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