kmail

kmfoldermbox.cpp

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