• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kmail

kmfoldermaildir.cpp

Go to the documentation of this file.
00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmfoldermaildir.cpp
00003 // Author: Kurt Granroth <granroth@kde.org>
00004 
00005 #include "kmfoldermaildir.h"
00006 
00007 #include <QDir>
00008 #include <QRegExp>
00009 #include <QByteArray>
00010 #include <QFileInfo>
00011 
00012 #include <kpimutils/kfileio.h>
00013 #include "kmfoldermgr.h"
00014 #include "kmfolder.h"
00015 #include "undostack.h"
00016 #include "maildirjob.h"
00017 #include "kcursorsaver.h"
00018 #include "jobscheduler.h"
00019 using KMail::MaildirJob;
00020 #include "compactionjob.h"
00021 #include "kmmsgdict.h"
00022 #include "util.h"
00023 
00024 #include <kio/directorysizejob.h>
00025 
00026 #include <kdebug.h>
00027 #include <kde_file.h>
00028 #include <kfileitem.h>
00029 #include <kglobal.h>
00030 #include <klocale.h>
00031 #include <kmessagebox.h>
00032 #include <krandom.h>
00033 
00034 #include <QDateTime>
00035 #include <QPointer>
00036 
00037 #include <dirent.h>
00038 #include <errno.h>
00039 #include <stdlib.h>
00040 #include <sys/stat.h>
00041 #include <sys/types.h>
00042 #include <unistd.h>
00043 #include <assert.h>
00044 #include <limits.h>
00045 #include <fcntl.h>
00046 
00047 #ifndef MAX_LINE
00048 #define MAX_LINE 4096
00049 #endif
00050 #ifndef INIT_MSGS
00051 #define INIT_MSGS 8
00052 #endif
00053 
00054 using KPIMUtils::removeDirAndContentsRecursively;
00055 
00056 // A separator for "uniq:info" (see the original maildir specification // krazy:exclude=spelling
00057 // at http://cr.yp.to/proto/maildir.html.
00058 // Windows uses '!' character instead as ':' is not supported by the OS.
00059 // TODO make it configurable - jstaniek
00060 // TODO check what the choice for Thunderbird 3 - jstaniek
00061 #ifdef Q_WS_WIN
00062 #define KMAIL_MAILDIR_FNAME_SEPARATOR "!"
00063 #else
00064 #define KMAIL_MAILDIR_FNAME_SEPARATOR ":"
00065 #endif
00066 
00067 #ifdef KMAIL_SQLITE_INDEX
00068 #include <sqlite3.h>
00069 #endif
00070 
00071 //-----------------------------------------------------------------------------
00072 KMFolderMaildir::KMFolderMaildir(KMFolder* folder, const char* name)
00073   : KMFolderIndex(folder, name), mCurrentlyCheckingFolderSize(false)
00074 {
00075 
00076 }
00077 
00078 
00079 //-----------------------------------------------------------------------------
00080 KMFolderMaildir::~KMFolderMaildir()
00081 {
00082   if ( mOpenCount > 0 ) {
00083     close( "~foldermaildir", true );
00084   }
00085   if ( kmkernel->undoStack() ) {
00086     kmkernel->undoStack()->folderDestroyed( folder() );
00087   }
00088 }
00089 
00090 //-----------------------------------------------------------------------------
00091 bool KMFolderMaildir::canAccess() const
00092 {
00093   assert(!folder()->name().isEmpty());
00094 
00095   QString sBadFolderName;
00096   QStringList files;
00097   files << "" << "/new" << "/cur" << "/tmp";
00098   foreach( const QString& fname, files ) {
00099     QFileInfo finfo( location() + fname );
00100     if ( !finfo.isDir() || !finfo.isReadable() || !finfo.isWritable() ) {
00101       sBadFolderName = location() + fname;
00102       break;
00103     }
00104   }
00105 
00106   if ( sBadFolderName.isEmpty() )
00107     return true;
00108 
00109   KCursorSaver idle(KBusyPtr::idle());
00110   if ( !QFile::exists(sBadFolderName) )
00111     KMessageBox::sorry(0, i18n("Error opening %1; this folder is missing.",
00112                         sBadFolderName));
00113   else
00114     KMessageBox::sorry(0, i18n("Error opening %1; either this is not a valid "
00115                                "maildir folder, or you do not have sufficient access permissions.",
00116                         sBadFolderName));
00117   return false;
00118 }
00119 
00120 //-----------------------------------------------------------------------------
00121 int KMFolderMaildir::open( const char * )
00122 {
00123   mOpenCount++;
00124   kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
00125 
00126   if (mOpenCount > 1) return 0;  // already open
00127 
00128   assert(!folder()->name().isEmpty());
00129 
00130   if ( !canAccess() )
00131     return 1;
00132 
00133   int rc = openInternal( NoOptions );
00134 
00135   return rc;
00136 }
00137 
00138 //-----------------------------------------------------------------------------
00139 int KMFolderMaildir::createMaildirFolders( const QString &folderPath )
00140 {
00141   // Make sure that neither a new, cur or tmp subfolder exists already.
00142   QFileInfo dirinfo;
00143   dirinfo.setFile( folderPath + "/new" );
00144   if ( dirinfo.exists() ) {
00145     return EEXIST;
00146   }
00147 
00148   dirinfo.setFile( folderPath + "/cur" );
00149   if ( dirinfo.exists() ) {
00150     return EEXIST;
00151   }
00152 
00153   dirinfo.setFile( folderPath + "/tmp" );
00154   if ( dirinfo.exists() ) {
00155     return EEXIST;
00156   }
00157 
00158   // create the maildir directory structure
00159   if ( KDE_mkdir( QFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
00160     kDebug(5006) << "Could not create folder" << folderPath;
00161     return errno;
00162   }
00163   if ( KDE_mkdir( QFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
00164     kDebug(5006) << "Could not create folder" << folderPath << "/new";
00165     return errno;
00166   }
00167   if ( KDE_mkdir( QFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
00168     kDebug(5006) << "Could not create folder" << folderPath << "/cur";
00169     return errno;
00170   }
00171   if ( KDE_mkdir( QFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) {
00172     kDebug(5006) << "Could not create folder" << folderPath << "/tmp";
00173     return errno;
00174   }
00175 
00176   return 0; // no error
00177 }
00178 
00179 //-----------------------------------------------------------------------------
00180 int KMFolderMaildir::create()
00181 {
00182   assert(!folder()->name().isEmpty());
00183   assert(mOpenCount == 0);
00184 
00185   int rc = createMaildirFolders( location() );
00186   if ( rc != 0 )
00187     return rc;
00188 
00189   // FIXME no path == no index? - till
00190   return createInternal();
00191 }
00192 
00193 
00194 //-----------------------------------------------------------------------------
00195 void KMFolderMaildir::reallyDoClose()
00196 {
00197   if (mAutoCreateIndex)
00198   {
00199       updateIndex( true );
00200       writeConfig();
00201   }
00202 
00203   mMsgList.clear(true);
00204 
00205 #ifdef KMAIL_SQLITE_INDEX
00206   if ( mIndexDb ) {
00207     sqlite3_close( mIndexDb );
00208     mIndexDb = 0;
00209   }
00210 #else
00211     if (mIndexStream) {
00212     fclose(mIndexStream);
00213       kDebug( StorageDebug ) << "fclose(mIndexStream = " << mIndexStream << ")";
00214     updateIndexStreamPtr(true);
00215     }
00216   mIndexStream = 0;
00217 #endif
00218 
00219   mOpenCount   = 0;
00220   mUnreadMsgs  = -1;
00221 
00222   mMsgList.reset(INIT_MSGS);
00223 }
00224 
00225 //-----------------------------------------------------------------------------
00226 void KMFolderMaildir::sync()
00227 {
00228 #ifdef KMAIL_SQLITE_INDEX
00229 #else
00230   if (mOpenCount > 0)
00231     if (!mIndexStream || fsync(fileno(mIndexStream))) {
00232     kmkernel->emergencyExit( i18n("Could not sync maildir folder.") );
00233     }
00234 #endif
00235 }
00236 
00237 //-----------------------------------------------------------------------------
00238 int KMFolderMaildir::expungeContents()
00239 {
00240   // nuke all messages in this folder now
00241   QDir d( location() + "/new" );
00242   QStringList files( d.entryList( QDir::Files | QDir::NoDotAndDotDot ) );
00243   foreach ( const QString& file, files )
00244     QFile::remove( d.filePath(file) );
00245 
00246   d.setPath(location() + "/cur");
00247   files = d.entryList( QDir::Files | QDir::NoDotAndDotDot );
00248   foreach ( const QString& file, files )
00249     QFile::remove( d.filePath( file ) );
00250 
00251   return 0;
00252 }
00253 
00254 int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const QStringList& entryList, bool& done )
00255 {
00256   QString subdirNew(location() + "/new/");
00257   QString subdirCur(location() + "/cur/");
00258 
00259   unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
00260                            qMin( mMsgList.count(), startIndex + nbMessages );
00261   //kDebug(5006) <<"KMFolderMaildir: compacting from" << startIndex <<" to" << stopIndex;
00262   for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
00263     KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
00264     if (!mi)
00265       continue;
00266 
00267     QString filename(mi->fileName());
00268     if (filename.isEmpty())
00269       continue;
00270 
00271     // first, make sure this isn't in the 'new' subdir
00272     if ( entryList.contains( filename ) )
00273       moveInternal(subdirNew + filename, subdirCur + filename, mi);
00274 
00275     // construct a valid filename.  if it's already valid, then
00276     // nothing happens
00277     filename = constructValidFileName( filename, mi->messageStatus() );
00278 
00279     // if the name changed, then we need to update the actual filename
00280     if (filename != mi->fileName())
00281     {
00282       moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi);
00283       mi->setFileName(filename);
00284       setDirty( true );
00285     }
00286 
00287 #if 0
00288     // we can't have any New messages at this point
00289     if (mi->isNew())
00290     {
00291       mi->setStatus(KMMsgStatusUnread);
00292       setDirty( true );
00293     }
00294 #endif
00295   }
00296   done = ( stopIndex == mMsgList.count() );
00297   return 0;
00298 }
00299 
00300 //-----------------------------------------------------------------------------
00301 int KMFolderMaildir::compact( bool silent )
00302 {
00303   KMail::MaildirCompactionJob* job = new KMail::MaildirCompactionJob( folder(), true /*immediate*/ );
00304   int rc = job->executeNow( silent );
00305   // Note that job autodeletes itself.
00306   return rc;
00307 }
00308 
00309 //-------------------------------------------------------------
00310 FolderJob*
00311 KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
00312                               KMFolder *folder,  const QString&, const AttachmentStrategy* ) const
00313 {
00314   MaildirJob *job = new MaildirJob( msg, jt, folder );
00315   job->setParentFolder( this );
00316   return job;
00317 }
00318 
00319 //-------------------------------------------------------------
00320 FolderJob*
00321 KMFolderMaildir::doCreateJob( QList<KMMessage*>& msgList, const QString& sets,
00322                               FolderJob::JobType jt, KMFolder *folder ) const
00323 {
00324   MaildirJob *job = new MaildirJob( msgList, sets, jt, folder );
00325   job->setParentFolder( this );
00326   return job;
00327 }
00328 
00329 //-------------------------------------------------------------
00330 int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return)
00331 {
00332   if (!canAddMsgNow(aMsg, index_return)) return 0;
00333   return addMsgInternal( aMsg, index_return );
00334 }
00335 
00336 //-------------------------------------------------------------
00337 int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return,
00338                                      bool stripUid )
00339 {
00340 /*
00341 QFile fileD0( "testdat_xx-kmfoldermaildir-0" );
00342 if( fileD0.open( QIODevice::WriteOnly ) ) {
00343     QDataStream ds( &fileD0 );
00344     ds.writeRawData( aMsg->asString(), aMsg->asString().length() );
00345     fileD0.close();  // If data is 0 we just create a zero length file.
00346 }
00347 */
00348   int idx(-1);
00349 
00350   // take message out of the folder it is currently in, if any
00351   KMFolder* msgParent = aMsg->parent();
00352   if (msgParent)
00353   {
00354     if (msgParent==folder() && !kmkernel->folderIsDraftOrOutbox(folder()))
00355         return 0;
00356 
00357     idx = msgParent->find(aMsg);
00358     msgParent->getMsg( idx );
00359   }
00360 
00361   aMsg->setStatusFields();
00362   if (aMsg->headerField("Content-Type").isEmpty())  // This might be added by
00363     aMsg->removeHeaderField("Content-Type");        // the line above
00364 
00365 
00366   const QString uidHeader = aMsg->headerField( "X-UID" );
00367   if ( !uidHeader.isEmpty() && stripUid )
00368     aMsg->removeHeaderField( "X-UID" );
00369 
00370   const QByteArray msgText = aMsg->asString();
00371 
00372   // Re-add the uid so that the take can make use of it, in case the
00373   // message is currently in an imap folder
00374   if ( !uidHeader.isEmpty() && stripUid )
00375     aMsg->setHeaderField( "X-UID", uidHeader );
00376 
00377   if ( msgText.isEmpty() ) {
00378     kDebug(5006) <<"Message added to folder `" << objectName() <<"' contains no data. Ignoring it.";
00379     return 0;
00380   }
00381 
00382   // make sure the filename has the correct extension
00383   QString filename = constructValidFileName( aMsg->fileName(), aMsg->messageStatus() );
00384 
00385   QString tmp_file(location() + "/tmp/");
00386   tmp_file += filename;
00387 
00388   if ( ! KPIMUtils::kByteArrayToFile( msgText, tmp_file, false, false, false ) )
00389     kmkernel->emergencyExit( i18n("Message could not be added to the folder, possibly disk space is low.") );
00390 
00391   QFile file(tmp_file);
00392 
00393   KMFolderOpener openThis(folder(), "maildir");
00394   if (openThis.openResult())
00395   {
00396     kDebug(5006) << openThis.openResult() << "of folder:" << label();
00397     return openThis.openResult();
00398   }
00399 
00400   // now move the file to the correct location
00401   QString new_loc(location() + "/cur/");
00402   new_loc += filename;
00403   if (moveInternal(tmp_file, new_loc, filename, aMsg->messageStatus()).isEmpty())
00404   {
00405     file.remove();
00406     return -1;
00407   }
00408 
00409   if (msgParent && idx >= 0)
00410     msgParent->take(idx);
00411 
00412   // just to be sure it does not end up in the index
00413   if ( stripUid ) aMsg->setUID( 0 );
00414 
00415   if (filename != aMsg->fileName())
00416     aMsg->setFileName(filename);
00417 
00418   if ( aMsg->status().isUnread() || aMsg->status().isNew() ||
00419        folder() == kmkernel->outboxFolder())
00420   {
00421     if (mUnreadMsgs == -1)
00422       mUnreadMsgs = 1;
00423     else
00424       ++mUnreadMsgs;
00425     if ( !mQuiet ) {
00426       kDebug( 5006 ) << "FolderStorage::msgStatusChanged";
00427       emit numUnreadMsgsChanged( folder() );
00428     }else{
00429       if ( !mEmitChangedTimer->isActive() ) {
00430 //        kDebug( 5006 )<<"QuietTimer started";
00431         mEmitChangedTimer->start( 3000 );
00432       }
00433       mChanged = true;
00434     }
00435   }
00436   ++mTotalMsgs;
00437   mSize = -1;
00438 
00439   if ( aMsg->attachmentState() == KMMsgAttachmentUnknown &&
00440        aMsg->readyToShow() )
00441     aMsg->updateAttachmentState();
00442 
00443   // store information about the position in the folder file in the message
00444   aMsg->setParent(folder());
00445   aMsg->setMsgSize( msgText.length() );
00446   idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums );
00447   const unsigned long msgSerNum = aMsg->getMsgSerNum();
00448   kDebug( Test1Area ) << "getMsgSerNum:" << msgSerNum;
00449   if ( msgSerNum <= 0 )
00450     aMsg->setMsgSerNum();
00451   else
00452     replaceMsgSerNum( msgSerNum, &aMsg->toMsgBase(), idx );
00453 
00454   // write index entry if desired
00455   if (mAutoCreateIndex)
00456   {
00457 #ifdef KMAIL_SQLITE_INDEX
00458     // reset the db id, in case we have one, we are about to change folders
00459     // and can't reuse it there
00460     aMsg->setDbId( 0 );
00461     aMsg->setDirty( true );
00462 #else
00463     assert(mIndexStream != 0);
00464     clearerr(mIndexStream);
00465     KDE_fseek(mIndexStream, 0, SEEK_END);
00466     off_t revert = KDE_ftell(mIndexStream);
00467 #endif
00468 
00469     KMMsgBase * mb = &aMsg->toMsgBase();
00470     int error = writeMessages( mb, true /*flush*/ );
00471     if ( error )
00472     kDebug(5006) << "Error: writing the index for this folder failed";
00473 
00474     if ( mExportsSernums )
00475       error |= appendToFolderIdsFile( idx );
00476 
00477     if ( error ) {
00478       kDebug(5006) << "Error: Could not add message to folder (No space left on device?)";
00479 #ifdef KMAIL_SQLITE_INDEX
00480 #else
00481       if ( KDE_ftell( mIndexStream ) > revert ) {
00482     kDebug(5006) << "Undoing changes";
00483     truncate( QFile::encodeName( indexLocation() ), revert );
00484       }
00485 #endif
00486       kmkernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss."));
00487       // exit(1); // don't ever use exit(), use the above!
00488 
00489       /* This code may not be 100% reliable
00490       bool busy = kmkernel->kbp()->isBusy();
00491       if (busy) kmkernel->kbp()->idle();
00492       KMessageBox::sorry(0,
00493         i18n("Unable to add message to folder.\n"
00494          "(No space left on device or insufficient quota?)\n"
00495          "Free space and sufficient quota are required to continue safely."));
00496       if (busy) kmkernel->kbp()->busy();
00497       */
00498       return error;
00499     }
00500   }
00501 
00502   if (index_return)
00503     *index_return = idx;
00504 
00505   emitMsgAddedSignals(idx);
00506   needsCompact = true;
00507 
00508 /*
00509 QFile fileD1( "testdat_xx-kmfoldermaildir-1" );
00510 if( fileD1.open( QIODevice::WriteOnly ) ) {
00511     QDataStream ds( &fileD1 );
00512     ds.writeRawData( aMsg->asString(), aMsg->asString().length() );
00513     fileD1.close();  // If data is 0 we just create a zero length file.
00514 }
00515 */
00516   return 0;
00517 }
00518 
00519 KMMessage* KMFolderMaildir::readMsg(int idx)
00520 {
00521   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00522   KMMessage *msg = new KMMessage(*mi); // note that mi is deleted by the line below
00523   mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
00524   msg->setComplete( true );
00525   msg->fromDwString(getDwString(idx));
00526   return msg;
00527 }
00528 
00529 DwString KMFolderMaildir::getDwString(int idx)
00530 {
00531   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00532   QString abs_file(location() + "/cur/");
00533   abs_file += mi->fileName();
00534   QFileInfo fi( abs_file );
00535 
00536   if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0)
00537   {
00538     FILE* stream = KDE_fopen(QFile::encodeName(abs_file), "r+");
00539     kDebug( StorageDebug ) << "KDE_fopen(abs_file=" << abs_file << ", \"r+\") == stream == " << stream;
00540     if (stream) {
00541       size_t msgSize = fi.size();
00542       char* msgText = new char[ msgSize + 1 ];
00543       fread(msgText, msgSize, 1, stream);
00544       fclose( stream );
00545       kDebug( StorageDebug ) << "fclose(mIndexStream = " << stream << ")";
00546       msgText[msgSize] = '\0';
00547       size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize );
00548       DwString str;
00549       // the DwString takes possession of msgText, so we must not delete it
00550       str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
00551       return str;
00552     }
00553   }
00554   kDebug(5006) <<"Could not open file r+" << abs_file;
00555   return DwString();
00556 }
00557 
00558 
00559 void KMFolderMaildir::readFileHeaderIntern( const QString& dir,
00560                                             const QString& file,
00561                                             MessageStatus& status )
00562 {
00563   // we keep our current directory to restore it later
00564   char path_buffer[PATH_MAX];
00565   if ( !::getcwd( path_buffer, PATH_MAX - 1 ) ) {
00566     return;
00567   }
00568 
00569   ::chdir( QFile::encodeName( dir ) );
00570 
00571   // messages in the 'cur' directory are Read by default.. but may
00572   // actually be some other state (but not New)
00573   if ( status.isRead() ) {
00574     if ( !file.contains(KMAIL_MAILDIR_FNAME_SEPARATOR "2,") ) {
00575       status.setUnread();
00576     } else if ( file.right(5) == KMAIL_MAILDIR_FNAME_SEPARATOR "2,RS" ) {
00577       status.setReplied();
00578     }
00579   }
00580 
00581   // open the file and get a pointer to it
00582   QFile f( file );
00583   if ( f.open( QIODevice::ReadOnly ) == false ) {
00584     kWarning(5006) <<"The file '" << QFile::encodeName(dir) <<"/" << file
00585                    << "' could not be opened for reading the message."
00586                    << "Please check ownership and permissions.";
00587     return;
00588   }
00589 
00590   char line[MAX_LINE];
00591   bool atEof    = false;
00592   bool inHeader = true;
00593   QByteArray *lastStr = 0;
00594 
00595   QByteArray dateStr, fromStr, toStr, subjStr;
00596   QByteArray xmarkStr, replyToIdStr, msgIdStr, referencesStr;
00597   QByteArray statusStr, replyToAuxIdStr, uidStr;
00598   QByteArray contentTypeStr, charset;
00599 
00600   // iterate through this file until done
00601   while (!atEof) {
00602     // if the end of the file has been reached or if there was an error
00603     if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) ) {
00604       atEof = true;
00605     }
00606 
00607     // are we done with this file?  if so, compile our info and store
00608     // it in a KMMsgInfo object
00609     if (atEof || !inHeader)
00610     {
00611       msgIdStr = msgIdStr.trimmed();
00612       if( !msgIdStr.isEmpty() ) {
00613         int rightAngle;
00614         rightAngle = msgIdStr.indexOf( '>' );
00615         if( rightAngle != -1 )
00616           msgIdStr.truncate( rightAngle + 1 );
00617       }
00618 
00619       replyToIdStr = replyToIdStr.trimmed();
00620       if( !replyToIdStr.isEmpty() ) {
00621         int rightAngle;
00622         rightAngle = replyToIdStr.indexOf( '>' );
00623         if( rightAngle != -1 )
00624           replyToIdStr.truncate( rightAngle + 1 );
00625       }
00626 
00627       referencesStr = referencesStr.trimmed();
00628       if( !referencesStr.isEmpty() ) {
00629         int leftAngle, rightAngle;
00630         leftAngle = referencesStr.lastIndexOf( '<' );
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.lastIndexOf( '<', leftAngle - 1 );
00639         if( leftAngle != -1 )
00640           referencesStr = referencesStr.mid( leftAngle );
00641         rightAngle = referencesStr.lastIndexOf( '>' );
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.indexOf( '>' );
00651         if( rightAngle != -1 )
00652           replyToAuxIdStr.truncate( rightAngle + 1 );
00653       }
00654 
00655       statusStr = statusStr.trimmed();
00656       if (!statusStr.isEmpty())
00657       {
00658         // only handle those states not determined by the file suffix
00659         if (statusStr[0] == 'S')
00660           status.setSent();
00661         else if (statusStr[0] == 'F')
00662           status.setForwarded();
00663         else if (statusStr[0] == 'D')
00664           status.setDeleted();
00665         else if (statusStr[0] == 'Q')
00666           status.setQueued();
00667         else if (statusStr[0] == 'G')
00668           status.setImportant();
00669       }
00670 
00671       contentTypeStr = contentTypeStr.trimmed();
00672       charset = "";
00673       if ( !contentTypeStr.isEmpty() ) {
00674         int cidx = contentTypeStr.indexOf( "charset=" );
00675         if ( cidx != -1 ) {
00676           charset = contentTypeStr.mid( cidx + 8 );
00677           if ( charset[0] == '"' ) {
00678             charset = charset.mid( 1 );
00679           }
00680           cidx = 0;
00681           while ( cidx < charset.length() ) {
00682             if ( charset[cidx] == '"' ||
00683                  ( !isalnum(charset[cidx] ) &&
00684                    charset[cidx] != '-' && charset[cidx] != '_' ) ) {
00685               break;
00686             }
00687             ++cidx;
00688           }
00689           charset.truncate( cidx );
00690         }
00691       }
00692 
00693       KMMsgInfo *mi = new KMMsgInfo(folder());
00694       mi->init( subjStr.trimmed(),
00695                 fromStr.trimmed(),
00696                 toStr.trimmed(),
00697                 0, status,
00698                 xmarkStr.trimmed(),
00699                 replyToIdStr, replyToAuxIdStr, msgIdStr,
00700                 file.toLocal8Bit(),
00701                 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
00702                 KMMsgMDNStateUnknown, charset, f.size() );
00703 
00704       dateStr = dateStr.trimmed();
00705       if (!dateStr.isEmpty())
00706         mi->setDate(dateStr.constData());
00707       if ( !uidStr.isEmpty() )
00708          mi->setUID( uidStr.toULong() );
00709       mi->setDirty(false);
00710       mMsgList.append( mi, mExportsSernums );
00711 
00712       // if this is a New file and is in 'new', we move it to 'cur'
00713       if ( status.isNew() )
00714       {
00715         QString newDir(location() + "/new/");
00716         QString curDir(location() + "/cur/");
00717         moveInternal(newDir + file, curDir + file, mi);
00718       }
00719 
00720       break;
00721     }
00722 
00723     // Is this a long header line?
00724     if (inHeader && line[0] == '\t' || line[0] == ' ')
00725     {
00726       int i = 0;
00727       while (line[i] == '\t' || line[i] == ' ')
00728         i++;
00729       if (line[i] < ' ' && line[i] > 0)
00730         inHeader = false;
00731       else
00732         if (lastStr)
00733           *lastStr += line + i;
00734     }
00735     else
00736       lastStr = 0;
00737 
00738     if (inHeader && (line[0] == '\n' || line[0] == '\r'))
00739       inHeader = false;
00740     if (!inHeader)
00741       continue;
00742 
00743     if ( strncasecmp(line, "Date:", 5) == 0) {
00744       dateStr = QByteArray( line + 5 );
00745       lastStr = &dateStr;
00746     } else if ( strncasecmp( line, "From:", 5 ) == 0 ) {
00747       fromStr = QByteArray( line + 5 );
00748       lastStr = &fromStr;
00749     } else if ( strncasecmp( line, "To:", 3 ) == 0 ) {
00750       toStr = QByteArray( line + 3 );
00751       lastStr = &toStr;
00752     } else if ( strncasecmp( line, "Subject:", 8 ) == 0 ) {
00753       subjStr = QByteArray( line + 8 );
00754       lastStr = &subjStr;
00755     } else if ( strncasecmp( line, "References:", 11 ) == 0 ) {
00756       referencesStr = QByteArray( line + 11 );
00757       lastStr = &referencesStr;
00758     } else if ( strncasecmp( line, "Message-Id:", 11 ) == 0 ) {
00759       msgIdStr = QByteArray( line + 11 );
00760       lastStr = &msgIdStr;
00761     } else if ( strncasecmp( line, "X-KMail-Mark:", 13 ) == 0 ) {
00762       xmarkStr = QByteArray( line + 13 );
00763     } else if ( strncasecmp( line, "X-Status:", 9 ) == 0 ) {
00764       statusStr = QByteArray( line + 9 );
00765     } else if ( strncasecmp( line, "In-Reply-To:", 12 ) == 0 ) {
00766       replyToIdStr = QByteArray( line + 12 );
00767       lastStr = &replyToIdStr;
00768     } else if ( strncasecmp( line, "X-UID:", 6 ) == 0 ) {
00769       uidStr = QByteArray( line + 6 );
00770       lastStr = &uidStr;
00771     } else if ( strncasecmp( line, "Content-Type:", 13 ) == 0) {
00772       contentTypeStr = QByteArray( line + 13 );
00773       lastStr = &contentTypeStr;
00774     }
00775 
00776   }
00777 
00778   if ( status.isNew() || status.isUnread() ||
00779       (folder() == kmkernel->outboxFolder()))
00780   {
00781     mUnreadMsgs++;
00782    if (mUnreadMsgs == 0) ++mUnreadMsgs;
00783   }
00784 
00785   ::chdir(path_buffer);
00786 }
00787 
00788 int KMFolderMaildir::createIndexFromContents()
00789 {
00790   kDebug() << "Creating index for" << location();
00791 
00792   mUnreadMsgs = 0;
00793 
00794   mMsgList.clear(true);
00795   mMsgList.reset(INIT_MSGS);
00796 
00797   mChanged = false;
00798 
00799   // first, we make sure that all the directories are here as they
00800   // should be
00801   QFileInfo dirinfo;
00802 
00803   dirinfo.setFile(location() + "/new");
00804   if (!dirinfo.exists() || !dirinfo.isDir())
00805   {
00806     kDebug(5006) << "Directory" << location() <<"/new doesn't exist or is a file";
00807     return 1;
00808   }
00809   QDir newDir(location() + "/new");
00810   newDir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
00811 
00812   dirinfo.setFile(location() + "/cur");
00813   if (!dirinfo.exists() || !dirinfo.isDir())
00814   {
00815     kDebug(5006) << "Directory" << location() <<"/cur doesn't exist or is a file";
00816     return 1;
00817   }
00818   QDir curDir(location() + "/cur");
00819   curDir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
00820 
00821   // then, we look for all the 'cur' files
00822   foreach( const QFileInfo& fi, curDir.entryInfoList() )
00823   {
00824     MessageStatus st = MessageStatus::statusRead();
00825     readFileHeaderIntern( curDir.path(), fi.fileName(), st );
00826   }
00827 
00828   // then, we look for all the 'new' files
00829   foreach( const QFileInfo& fi, newDir.entryInfoList() )
00830   {
00831     MessageStatus st = MessageStatus::statusNew();
00832     readFileHeaderIntern( newDir.path(), fi.fileName(), st );
00833   }
00834 
00835   if ( autoCreateIndex() ) {
00836     emit statusMsg(i18n("Writing index file"));
00837     writeIndex();
00838   }
00839   else {
00840 #ifdef KMAIL_SQLITE_INDEX
00841 #else
00842     mHeaderOffset = 0;
00843 #endif
00844   }
00845 
00846   correctUnreadMsgsCount();
00847 
00848   if (kmkernel->outboxFolder() == folder() && count() > 0)
00849     KMessageBox::information(0, i18n("Your outbox contains messages which were "
00850     "most-likely not created by KMail;\nplease remove them from there if you "
00851     "do not want KMail to send them."));
00852 
00853   needsCompact = true;
00854 
00855   invalidateFolder();
00856   return 0;
00857 }
00858 
00859 KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus()
00860 {
00861   QFileInfo new_info(location() + "/new");
00862   QFileInfo cur_info(location() + "/cur");
00863   QFileInfo index_info(indexLocation());
00864 
00865   if (!index_info.exists())
00866     return KMFolderIndex::IndexMissing;
00867 
00868   // Check whether the directories are more than 5 seconds newer than the index
00869   // file. The 5 seconds are added to reduce the number of false alerts due
00870   // to slightly out of sync clocks of the NFS server and the local machine.
00871   KMFolderIndex::IndexStatus status =
00872       ( ( new_info.lastModified() > index_info.lastModified().addSecs( 5 ) ) ||
00873         ( cur_info.lastModified() > index_info.lastModified().addSecs( 5 ) ) )
00874          ? KMFolderIndex::IndexTooOld
00875          : KMFolderIndex::IndexOk;
00876   if ( status == KMFolderIndex::IndexTooOld ) {
00877     kWarning() << "Index" << indexLocation() << "out of date!";
00878     kWarning() << "  new:" << new_info.lastModified();
00879     kWarning() << "  cur:" << cur_info.lastModified();
00880     kWarning() << "  index:" << index_info.lastModified();
00881   }
00882   return status;
00883 }
00884 
00885 //-----------------------------------------------------------------------------
00886 void KMFolderMaildir::removeMsg(int idx, bool)
00887 {
00888   KMMsgBase* msg = mMsgList[idx];
00889   if (!msg || msg->fileName().isNull()) return;
00890 
00891   removeFile(msg->fileName());
00892 
00893   KMFolderIndex::removeMsg(idx);
00894 }
00895 
00896 //-----------------------------------------------------------------------------
00897 KMMessage* KMFolderMaildir::take(int idx)
00898 {
00899   // first, we do the high-level stuff.. then delete later
00900   KMMessage *msg = KMFolderIndex::take(idx);
00901 
00902   if (!msg || msg->fileName().isNull()) {
00903     return 0;
00904   }
00905 
00906   if ( removeFile(msg->fileName()) ) {
00907     return msg;
00908   } else {
00909     return 0;
00910   }
00911 }
00912 
00913 
00914 // static
00915 bool KMFolderMaildir::removeFile( const QString & folderPath,
00916                                   const QString & filename )
00917 {
00918   // we need to look in both 'new' and 'cur' since it's possible to
00919   // delete a message before the folder is compacted. Since the file
00920   // naming and moving is done in ::compact, we can't assume any
00921   // location at this point.
00922   QByteArray abs_file( QFile::encodeName( folderPath + "/cur/" + filename ) );
00923   if ( ::unlink( abs_file ) == 0 )
00924     return true;
00925 
00926   if ( errno == ENOENT ) { // doesn't exist
00927     abs_file = QFile::encodeName( folderPath + "/new/" + filename );
00928     if ( ::unlink( abs_file ) == 0 )
00929       return true;
00930   }
00931 
00932   kDebug(5006) <<"Can't delete" << abs_file << perror;
00933   return false;
00934 }
00935 
00936