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