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 #ifdef HAVE_CONFIG_H
00006 #include <config.h>
00007 #endif
00008 
00009 #include <qdir.h>
00010 #include <qregexp.h>
00011 
00012 #include <libkdepim/kfileio.h>
00013 #include "kmfoldermaildir.h"
00014 #include "kmfoldermgr.h"
00015 #include "kmfolder.h"
00016 #include "undostack.h"
00017 #include "maildirjob.h"
00018 #include "kcursorsaver.h"
00019 #include "jobscheduler.h"
00020 using KMail::MaildirJob;
00021 #include "compactionjob.h"
00022 #include "kmmsgdict.h"
00023 #include "util.h"
00024 
00025 #include <kapplication.h>
00026 #include <kdebug.h>
00027 #include <klocale.h>
00028 #include <kstaticdeleter.h>
00029 #include <kmessagebox.h>
00030 #include <kdirsize.h>
00031 
00032 #include <dirent.h>
00033 #include <errno.h>
00034 #include <stdlib.h>
00035 #include <sys/stat.h>
00036 #include <sys/types.h>
00037 #include <unistd.h>
00038 #include <assert.h>
00039 #include <limits.h>
00040 #include <ctype.h>
00041 #include <fcntl.h>
00042 
00043 #ifndef MAX_LINE
00044 #define MAX_LINE 4096
00045 #endif
00046 #ifndef INIT_MSGS
00047 #define INIT_MSGS 8
00048 #endif
00049 
00050 // define the static member
00051 QValueList<KMFolderMaildir::DirSizeJobQueueEntry> KMFolderMaildir::s_DirSizeJobQueue;
00052 
00053 //-----------------------------------------------------------------------------
00054 KMFolderMaildir::KMFolderMaildir(KMFolder* folder, const char* name)
00055   : KMFolderIndex(folder, name), mCurrentlyCheckingFolderSize(false)
00056 {
00057 
00058 }
00059 
00060 
00061 //-----------------------------------------------------------------------------
00062 KMFolderMaildir::~KMFolderMaildir()
00063 {
00064   if (mOpenCount>0) close("~foldermaildir", true);
00065   if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
00066 }
00067 
00068 //-----------------------------------------------------------------------------
00069 int KMFolderMaildir::canAccess()
00070 {
00071 
00072   assert(!folder()->name().isEmpty());
00073 
00074   QString sBadFolderName;
00075   if (access(QFile::encodeName(location()), R_OK | W_OK | X_OK) != 0) {
00076     sBadFolderName = location();
00077   } else if (access(QFile::encodeName(location() + "/new"), R_OK | W_OK | X_OK) != 0) {
00078     sBadFolderName = location() + "/new";
00079   } else if (access(QFile::encodeName(location() + "/cur"), R_OK | W_OK | X_OK) != 0) {
00080     sBadFolderName = location() + "/cur";
00081   } else if (access(QFile::encodeName(location() + "/tmp"), R_OK | W_OK | X_OK) != 0) {
00082     sBadFolderName = location() + "/tmp";
00083   }
00084 
00085   if ( !sBadFolderName.isEmpty() ) {
00086     int nRetVal = QFile::exists(sBadFolderName) ? EPERM : ENOENT;
00087     KCursorSaver idle(KBusyPtr::idle());
00088     if ( nRetVal == ENOENT )
00089       KMessageBox::sorry(0, i18n("Error opening %1; this folder is missing.")
00090                          .arg(sBadFolderName));
00091     else
00092       KMessageBox::sorry(0, i18n("Error opening %1; either this is not a valid "
00093                                  "maildir folder, or you do not have sufficient access permissions.")
00094                          .arg(sBadFolderName));
00095     return nRetVal;
00096   }
00097 
00098   return 0;
00099 }
00100 
00101 //-----------------------------------------------------------------------------
00102 int KMFolderMaildir::open(const char *)
00103 {
00104   int rc = 0;
00105 
00106   mOpenCount++;
00107   kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
00108 
00109   if (mOpenCount > 1) return 0;  // already open
00110 
00111   assert(!folder()->name().isEmpty());
00112 
00113   rc = canAccess();
00114   if ( rc != 0 ) {
00115       return rc;
00116   }
00117 
00118   if (!folder()->path().isEmpty())
00119   {
00120     if (KMFolderIndex::IndexOk != indexStatus()) // test if contents file has changed
00121     {
00122       QString str;
00123       mIndexStream = 0;
00124       str = i18n("Folder `%1' changed; recreating index.")
00125           .arg(name());
00126       emit statusMsg(str);
00127     } else {
00128       mIndexStream = fopen(QFile::encodeName(indexLocation()), "r+"); // index file
00129       if ( mIndexStream ) {
00130         fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00131         updateIndexStreamPtr();
00132       }
00133     }
00134 
00135     if (!mIndexStream)
00136       rc = createIndexFromContents();
00137     else
00138       readIndex();
00139   }
00140   else
00141   {
00142     mAutoCreateIndex = false;
00143     rc = createIndexFromContents();
00144   }
00145 
00146   mChanged = false;
00147 
00148   //readConfig();
00149 
00150   return rc;
00151 }
00152 
00153 
00154 //-----------------------------------------------------------------------------
00155 int KMFolderMaildir::createMaildirFolders( const QString & folderPath )
00156 {
00157   // Make sure that neither a new, cur or tmp subfolder exists already.
00158   QFileInfo dirinfo;
00159   dirinfo.setFile( folderPath + "/new" );
00160   if ( dirinfo.exists() ) return EEXIST;
00161   dirinfo.setFile( folderPath + "/cur" );
00162   if ( dirinfo.exists() ) return EEXIST;
00163   dirinfo.setFile( folderPath + "/tmp" );
00164   if ( dirinfo.exists() ) return EEXIST;
00165 
00166   // create the maildir directory structure
00167   if ( ::mkdir( QFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
00168     kdDebug(5006) << "Could not create folder " << folderPath << endl;
00169     return errno;
00170   }
00171   if ( ::mkdir( QFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
00172     kdDebug(5006) << "Could not create folder " << folderPath << "/new" << endl;
00173     return errno;
00174   }
00175   if ( ::mkdir( QFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
00176     kdDebug(5006) << "Could not create folder " << folderPath << "/cur" << endl;
00177     return errno;
00178   }
00179   if ( ::mkdir( QFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) {
00180     kdDebug(5006) << "Could not create folder " << folderPath << "/tmp" << endl;
00181     return errno;
00182   }
00183 
00184   return 0; // no error
00185 }
00186 
00187 //-----------------------------------------------------------------------------
00188 int KMFolderMaildir::create()
00189 {
00190   int rc;
00191   int old_umask;
00192 
00193   assert(!folder()->name().isEmpty());
00194   assert(mOpenCount == 0);
00195 
00196   rc = createMaildirFolders( location() );
00197   if ( rc != 0 )
00198     return rc;
00199 
00200   // FIXME no path == no index? - till
00201   if (!folder()->path().isEmpty())
00202   {
00203     old_umask = umask(077);
00204     mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW
00205     updateIndexStreamPtr(true);
00206     umask(old_umask);
00207 
00208     if (!mIndexStream) return errno;
00209     fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00210   }
00211   else
00212   {
00213     mAutoCreateIndex = false;
00214   }
00215 
00216   mOpenCount++;
00217   mChanged = false;
00218 
00219   rc = writeIndex();
00220   return rc;
00221 }
00222 
00223 
00224 //-----------------------------------------------------------------------------
00225 void KMFolderMaildir::reallyDoClose(const char* owner)
00226 {
00227   if (mAutoCreateIndex)
00228   {
00229       updateIndex();
00230       writeConfig();
00231   }
00232 
00233   mMsgList.clear(true);
00234 
00235   if (mIndexStream) {
00236     fclose(mIndexStream);
00237     updateIndexStreamPtr(true);
00238   }
00239 
00240   mOpenCount   = 0;
00241   mIndexStream = 0;
00242   mUnreadMsgs  = -1;
00243 
00244   mMsgList.reset(INIT_MSGS);
00245 }
00246 
00247 //-----------------------------------------------------------------------------
00248 void KMFolderMaildir::sync()
00249 {
00250   if (mOpenCount > 0)
00251     if (!mIndexStream || fsync(fileno(mIndexStream))) {
00252     kmkernel->emergencyExit( i18n("Could not sync maildir folder.") );
00253     }
00254 }
00255 
00256 //-----------------------------------------------------------------------------
00257 int KMFolderMaildir::expungeContents()
00258 {
00259   // nuke all messages in this folder now
00260   QDir d(location() + "/new");
00261   // d.setFilter(QDir::Files); coolo: QFile::remove returns false for non-files
00262   QStringList files(d.entryList());
00263   QStringList::ConstIterator it(files.begin());
00264   for ( ; it != files.end(); ++it)
00265     QFile::remove(d.filePath(*it));
00266 
00267   d.setPath(location() + "/cur");
00268   files = d.entryList();
00269   for (it = files.begin(); it != files.end(); ++it)
00270     QFile::remove(d.filePath(*it));
00271 
00272   return 0;
00273 }
00274 
00275 int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const QStringList& entryList, bool& done )
00276 {
00277   QString subdirNew(location() + "/new/");
00278   QString subdirCur(location() + "/cur/");
00279 
00280   unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
00281                            QMIN( mMsgList.count(), startIndex + nbMessages );
00282   //kdDebug(5006) << "KMFolderMaildir: compacting from " << startIndex << " to " << stopIndex << endl;
00283   for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
00284     KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
00285     if (!mi)
00286       continue;
00287 
00288     QString filename(mi->fileName());
00289     if (filename.isEmpty())
00290       continue;
00291 
00292     // first, make sure this isn't in the 'new' subdir
00293     if ( entryList.contains( filename ) )
00294       moveInternal(subdirNew + filename, subdirCur + filename, mi);
00295 
00296     // construct a valid filename.  if it's already valid, then
00297     // nothing happens
00298     filename = constructValidFileName( filename, mi->status() );
00299 
00300     // if the name changed, then we need to update the actual filename
00301     if (filename != mi->fileName())
00302     {
00303       moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi);
00304       mi->setFileName(filename);
00305       setDirty( true );
00306     }
00307 
00308 #if 0
00309     // we can't have any New messages at this point
00310     if (mi->isNew())
00311     {
00312       mi->setStatus(KMMsgStatusUnread);
00313       setDirty( true );
00314     }
00315 #endif
00316   }
00317   done = ( stopIndex == mMsgList.count() );
00318   return 0;
00319 }
00320 
00321 //-----------------------------------------------------------------------------
00322 int KMFolderMaildir::compact( bool silent )
00323 {
00324   KMail::MaildirCompactionJob* job = new KMail::MaildirCompactionJob( folder(), true /*immediate*/ );
00325   int rc = job->executeNow( silent );
00326   // Note that job autodeletes itself.
00327   return rc;
00328 }
00329 
00330 //-------------------------------------------------------------
00331 FolderJob*
00332 KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
00333                               KMFolder *folder, QString, const AttachmentStrategy* ) const
00334 {
00335   MaildirJob *job = new MaildirJob( msg, jt, folder );
00336   job->setParentFolder( this );
00337   return job;
00338 }
00339 
00340 //-------------------------------------------------------------
00341 FolderJob*
00342 KMFolderMaildir::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
00343                               FolderJob::JobType jt, KMFolder *folder ) const
00344 {
00345   MaildirJob *job = new MaildirJob( msgList, sets, jt, folder );
00346   job->setParentFolder( this );
00347   return job;
00348 }
00349 
00350 //-------------------------------------------------------------
00351 int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return)
00352 {
00353   if (!canAddMsgNow(aMsg, index_return)) return 0;
00354   return addMsgInternal( aMsg, index_return );
00355 }
00356 
00357 //-------------------------------------------------------------
00358 int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return,
00359                                      bool stripUid )
00360 {
00361 /*
00362 QFile fileD0( "testdat_xx-kmfoldermaildir-0" );
00363 if( fileD0.open( IO_WriteOnly ) ) {
00364     QDataStream ds( &fileD0 );
00365     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00366     fileD0.close();  // If data is 0 we just create a zero length file.
00367 }
00368 */
00369   long len;
00370   unsigned long size;
00371   KMFolder* msgParent;
00372   QCString msgText;
00373   int idx(-1);
00374   int rc;
00375 
00376   // take message out of the folder it is currently in, if any
00377   msgParent = aMsg->parent();
00378   if (msgParent)
00379   {
00380     if (msgParent==folder() && !kmkernel->folderIsDraftOrOutbox(folder()))
00381         return 0;
00382 
00383     idx = msgParent->find(aMsg);
00384     msgParent->getMsg( idx );
00385   }
00386 
00387   aMsg->setStatusFields();
00388   if (aMsg->headerField("Content-Type").isEmpty())  // This might be added by
00389     aMsg->removeHeaderField("Content-Type");        // the line above
00390 
00391 
00392   const QString uidHeader = aMsg->headerField( "X-UID" );
00393   if ( !uidHeader.isEmpty() && stripUid )
00394     aMsg->removeHeaderField( "X-UID" );
00395 
00396   msgText = aMsg->asString(); // TODO use asDwString instead
00397   len = msgText.length();
00398 
00399   // Re-add the uid so that the take can make use of it, in case the
00400   // message is currently in an imap folder
00401   if ( !uidHeader.isEmpty() && stripUid )
00402     aMsg->setHeaderField( "X-UID", uidHeader );
00403 
00404   if (len <= 0)
00405   {
00406     kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
00407     return 0;
00408   }
00409 
00410   // make sure the filename has the correct extension
00411   QString filename = constructValidFileName( aMsg->fileName(), aMsg->status() );
00412 
00413   QString tmp_file(location() + "/tmp/");
00414   tmp_file += filename;
00415 
00416   if (!KPIM::kCStringToFile(msgText, tmp_file, false, false, false))
00417     kmkernel->emergencyExit( i18n("Message could not be added to the folder, possibly disk space is low.") );
00418 
00419   QFile file(tmp_file);
00420   size = msgText.length();
00421 
00422   KMFolderOpener openThis(folder(), "maildir");
00423   rc = openThis.openResult();
00424   if (rc)
00425   {
00426     kdDebug(5006) << "KMFolderMaildir::addMsg-open: " << rc << " of folder: " << label() << endl;
00427     return rc;
00428   }
00429 
00430   // now move the file to the correct location
00431   QString new_loc(location() + "/cur/");
00432   new_loc += filename;
00433   if (moveInternal(tmp_file, new_loc, filename, aMsg->status()).isNull())
00434   {
00435     file.remove();
00436     return -1;
00437   }
00438 
00439   if (msgParent && idx >= 0)
00440     msgParent->take(idx);
00441 
00442   // just to be sure it does not end up in the index
00443   if ( stripUid ) aMsg->setUID( 0 );
00444 
00445   if (filename != aMsg->fileName())
00446     aMsg->setFileName(filename);
00447 
00448   if (aMsg->isUnread() || aMsg->isNew() || folder() == kmkernel->outboxFolder())
00449   {
00450     if (mUnreadMsgs == -1)
00451       mUnreadMsgs = 1;
00452     else
00453       ++mUnreadMsgs;
00454     if ( !mQuiet ) {
00455       kdDebug( 5006 ) << "FolderStorage::msgStatusChanged" << endl;
00456       emit numUnreadMsgsChanged( folder() );
00457     }else{
00458       if ( !mEmitChangedTimer->isActive() ) {
00459 //        kdDebug( 5006 )<< "QuietTimer started" << endl;
00460         mEmitChangedTimer->start( 3000 );
00461       }
00462       mChanged = true;
00463     }
00464   }
00465   ++mTotalMsgs;
00466   mSize = -1;
00467 
00468   if ( aMsg->attachmentState() == KMMsgAttachmentUnknown &&
00469        aMsg->readyToShow() )
00470     aMsg->updateAttachmentState();
00471 
00472   // store information about the position in the folder file in the message
00473   aMsg->setParent(folder());
00474   aMsg->setMsgSize(size);
00475   idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums );
00476   if (aMsg->getMsgSerNum() <= 0)
00477     aMsg->setMsgSerNum();
00478   else
00479     replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
00480 
00481   // write index entry if desired
00482   if (mAutoCreateIndex)
00483   {
00484     assert(mIndexStream != 0);
00485     clearerr(mIndexStream);
00486     fseek(mIndexStream, 0, SEEK_END);
00487     off_t revert = ftell(mIndexStream);
00488 
00489     int len;
00490     KMMsgBase * mb = &aMsg->toMsgBase();
00491     const uchar *buffer = mb->asIndexString(len);
00492     fwrite(&len,sizeof(len), 1, mIndexStream);
00493     mb->setIndexOffset( ftell(mIndexStream) );
00494     mb->setIndexLength( len );
00495     if(fwrite(buffer, len, 1, mIndexStream) != 1)
00496       kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
00497 
00498     fflush(mIndexStream);
00499     int error = ferror(mIndexStream);
00500 
00501     if ( mExportsSernums )
00502       error |= appendToFolderIdsFile( idx );
00503 
00504     if (error) {
00505       kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
00506       if (ftell(mIndexStream) > revert) {
00507     kdDebug(5006) << "Undoing changes" << endl;
00508     truncate( QFile::encodeName(indexLocation()), revert );
00509       }
00510       kmkernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss."));
00511       // exit(1); // don't ever use exit(), use the above!
00512 
00513       /* This code may not be 100% reliable
00514       bool busy = kmkernel->kbp()->isBusy();
00515       if (busy) kmkernel->kbp()->idle();
00516       KMessageBox::sorry(0,
00517         i18n("Unable to add message to folder.\n"
00518          "(No space left on device or insufficient quota?)\n"
00519          "Free space and sufficient quota are required to continue safely."));
00520       if (busy) kmkernel->kbp()->busy();
00521       */
00522       return error;
00523     }
00524   }
00525 
00526   if (index_return)
00527     *index_return = idx;
00528 
00529   emitMsgAddedSignals(idx);
00530   needsCompact = true;
00531 
00532 /*
00533 QFile fileD1( "testdat_xx-kmfoldermaildir-1" );
00534 if( fileD1.open( IO_WriteOnly ) ) {
00535     QDataStream ds( &fileD1 );
00536     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00537     fileD1.close();  // If data is 0 we just create a zero length file.
00538 }
00539 */
00540   return 0;
00541 }
00542 
00543 KMMessage* KMFolderMaildir::readMsg(int idx)
00544 {
00545   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00546   KMMessage *msg = new KMMessage(*mi);
00547   msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
00548   mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
00549   msg->setComplete( true );
00550   msg->fromDwString(getDwString(idx));
00551   return msg;
00552 }
00553 
00554 DwString KMFolderMaildir::getDwString(int idx)
00555 {
00556   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00557   QString abs_file(location() + "/cur/");
00558   abs_file += mi->fileName();
00559   QFileInfo fi( abs_file );
00560 
00561   if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0)
00562   {
00563     FILE* stream = fopen(QFile::encodeName(abs_file), "r+");
00564     if (stream) {
00565       size_t msgSize = fi.size();
00566       char* msgText = new char[ msgSize + 1 ];
00567       fread(msgText, msgSize, 1, stream);
00568       fclose( stream );
00569       msgText[msgSize] = '\0';
00570       size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize );
00571       DwString str;
00572       // the DwString takes possession of msgText, so we must not delete it
00573       str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
00574       return str;
00575     }
00576   }
00577   kdDebug(5006) << "Could not open file r+ " << abs_file << endl;
00578   return DwString();
00579 }
00580 
00581 
00582 void KMFolderMaildir::readFileHeaderIntern(const QString& dir, const QString& file, KMMsgStatus status)
00583 {
00584   // we keep our current directory to restore it later
00585   char path_buffer[PATH_MAX];
00586   if(!::getcwd(path_buffer, PATH_MAX - 1))
00587     return;
00588 
00589   ::chdir(QFile::encodeName(dir));
00590 
00591   // messages in the 'cur' directory are Read by default.. but may
00592   // actually be some other state (but not New)
00593   if (status == KMMsgStatusRead)
00594   {
00595     if (file.find(":2,") == -1)
00596       status = KMMsgStatusUnread;
00597     else if (file.right(5) == ":2,RS")
00598       status |= KMMsgStatusReplied;
00599   }
00600 
00601   // open the file and get a pointer to it
00602   QFile f(file);
00603   if ( f.open( IO_ReadOnly ) == false ) {
00604     kdWarning(5006) << "The file '" << QFile::encodeName(dir) << "/" << file
00605                     << "' could not be opened for reading the message. "
00606                        "Please check ownership and permissions."
00607                     << endl;
00608     return;
00609   }
00610 
00611   char line[MAX_LINE];
00612   bool atEof    = false;
00613   bool inHeader = true;
00614   QCString *lastStr = 0;
00615 
00616   QCString dateStr, fromStr, toStr, subjStr;
00617   QCString xmarkStr, replyToIdStr, msgIdStr, referencesStr;
00618   QCString statusStr, replyToAuxIdStr, uidStr;
00619   QCString contentTypeStr, charset;
00620 
00621   // iterate through this file until done
00622   while (!atEof)
00623   {
00624     // if the end of the file has been reached or if there was an error
00625     if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) )
00626       atEof = true;
00627 
00628     // are we done with this file?  if so, compile our info and store
00629     // it in a KMMsgInfo object
00630     if (atEof || !inHeader)
00631     {
00632       msgIdStr = msgIdStr.stripWhiteSpace();
00633       if( !msgIdStr.isEmpty() ) {
00634         int rightAngle;
00635         rightAngle = msgIdStr.find( '>' );
00636         if( rightAngle != -1 )
00637           msgIdStr.truncate( rightAngle + 1 );
00638       }
00639 
00640       replyToIdStr = replyToIdStr.stripWhiteSpace();
00641       if( !replyToIdStr.isEmpty() ) {
00642         int rightAngle;
00643         rightAngle = replyToIdStr.find( '>' );
00644         if( rightAngle != -1 )
00645           replyToIdStr.truncate( rightAngle + 1 );
00646       }
00647 
00648       referencesStr = referencesStr.stripWhiteSpace();
00649       if( !referencesStr.isEmpty() ) {
00650         int leftAngle, rightAngle;
00651         leftAngle = referencesStr.findRev( '<' );
00652         if( ( leftAngle != -1 )
00653             && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
00654           // use the last reference, instead of missing In-Reply-To
00655           replyToIdStr = referencesStr.mid( leftAngle );
00656         }
00657 
00658         // find second last reference
00659         leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
00660         if( leftAngle != -1 )
00661           referencesStr = referencesStr.mid( leftAngle );
00662         rightAngle = referencesStr.findRev( '>' );
00663         if( rightAngle != -1 )
00664           referencesStr.truncate( rightAngle + 1 );
00665 
00666         // Store the second to last reference in the replyToAuxIdStr
00667         // It is a good candidate for threading the message below if the
00668         // message In-Reply-To points to is not kept in this folder,
00669         // but e.g. in an Outbox
00670         replyToAuxIdStr = referencesStr;
00671         rightAngle = referencesStr.find( '>' );
00672         if( rightAngle != -1 )
00673           replyToAuxIdStr.truncate( rightAngle + 1 );
00674       }
00675 
00676       statusStr = statusStr.stripWhiteSpace();
00677       if (!statusStr.isEmpty())
00678       {
00679         // only handle those states not determined by the file suffix
00680         if (statusStr[0] == 'S')
00681           status |= KMMsgStatusSent;
00682         else if (statusStr[0] == 'F')
00683           status |= KMMsgStatusForwarded;
00684         else if (statusStr[0] == 'D')
00685           status |= KMMsgStatusDeleted;
00686         else if (statusStr[0] == 'Q')
00687           status |= KMMsgStatusQueued;
00688         else if (statusStr[0] == 'G')
00689           status |= KMMsgStatusFlag;
00690       }
00691 
00692       contentTypeStr = contentTypeStr.stripWhiteSpace();
00693       charset = "";
00694       if ( !contentTypeStr.isEmpty() )
00695       {
00696         int cidx = contentTypeStr.find( "charset=" );
00697         if ( cidx != -1 ) {
00698           charset = contentTypeStr.mid( cidx + 8 );
00699           if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
00700             charset = charset.mid( 1 );
00701           }
00702           cidx = 0;
00703           while ( (unsigned int) cidx < charset.length() ) {
00704             if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
00705                  charset[cidx] != '-' && charset[cidx] != '_' ) )
00706               break;
00707             ++cidx;
00708           }
00709           charset.truncate( cidx );
00710           // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
00711           //              charset << " from " << contentTypeStr << endl;
00712         }
00713       }
00714 
00715       KMMsgInfo *mi = new KMMsgInfo(folder());
00716       mi->init( subjStr.stripWhiteSpace(),
00717                 fromStr.stripWhiteSpace(),
00718                 toStr.stripWhiteSpace(),
00719                 0, status,
00720                 xmarkStr.stripWhiteSpace(),
00721                 replyToIdStr, replyToAuxIdStr, msgIdStr,
00722                 file.local8Bit(),
00723                 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
00724                 KMMsgMDNStateUnknown, charset, f.size() );
00725 
00726       dateStr = dateStr.stripWhiteSpace();
00727       if (!dateStr.isEmpty())
00728         mi->setDate(dateStr);
00729       if ( !uidStr.isEmpty() )
00730          mi->setUID( uidStr.toULong() );
00731       mi->setDirty(false);
00732       mMsgList.append( mi, mExportsSernums );
00733 
00734       // if this is a New file and is in 'new', we move it to 'cur'
00735       if (status & KMMsgStatusNew)
00736       {
00737         QString newDir(location() + "/new/");
00738         QString curDir(location() + "/cur/");
00739         moveInternal(newDir + file, curDir + file, mi);
00740       }
00741 
00742       break;
00743     }
00744 
00745     // Is this a long header line?
00746     if (inHeader && line[0] == '\t' || line[0] == ' ')
00747     {
00748       int i = 0;
00749       while (line[i] == '\t' || line[i] == ' ')
00750         i++;
00751       if (line[i] < ' ' && line[i] > 0)
00752         inHeader = false;
00753       else
00754         if (lastStr)
00755           *lastStr += line + i;
00756     }
00757     else
00758       lastStr = 0;
00759 
00760     if (inHeader && (line[0] == '\n' || line[0] == '\r'))
00761       inHeader = false;
00762     if (!inHeader)
00763       continue;
00764 
00765     if (strncasecmp(line, "Date:", 5) == 0)
00766     {
00767       dateStr = QCString(line+5);
00768       lastStr = &dateStr;
00769     }
00770     else if (strncasecmp(line, "From:", 5) == 0)
00771     {
00772       fromStr = QCString(line+5);
00773       lastStr = &fromStr;
00774     }
00775     else if (strncasecmp(line, "To:", 3) == 0)
00776     {
00777       toStr = QCString(line+3);
00778       lastStr = &toStr;
00779     }
00780     else if (strncasecmp(line, "Subject:", 8) == 0)
00781     {
00782       subjStr = QCString(line+8);
00783       lastStr = &subjStr;
00784     }
00785     else if (strncasecmp(line, "References:", 11) == 0)
00786     {
00787       referencesStr = QCString(line+11);
00788       lastStr = &referencesStr;
00789     }
00790     else if (strncasecmp(line, "Message-Id:", 11) == 0)
00791     {
00792       msgIdStr = QCString(line+11);
00793       lastStr = &msgIdStr;
00794     }
00795     else if (strncasecmp(line, "X-KMail-Mark:", 13) == 0)
00796     {
00797       xmarkStr = QCString(line+13);
00798     }
00799     else if (strncasecmp(line, "X-Status:", 9) == 0)
00800     {
00801       statusStr = QCString(line+9);
00802     }
00803     else if (strncasecmp(line, "In-Reply-To:", 12) == 0)
00804     {
00805       replyToIdStr = QCString(line+12);
00806       lastStr = &replyToIdStr;
00807     }
00808     else if (strncasecmp(line, "X-UID:", 6) == 0)
00809     {
00810       uidStr = QCString(line+6);
00811       lastStr = &uidStr;
00812     }
00813     else if (strncasecmp(line, "Content-Type:", 13) == 0)
00814     {
00815       contentTypeStr = QCString(line+13);
00816       lastStr = &contentTypeStr;
00817     }
00818 
00819   }
00820 
00821   if (status & KMMsgStatusNew || status & KMMsgStatusUnread ||
00822       (folder() == kmkernel->outboxFolder()))
00823   {
00824     mUnreadMsgs++;
00825    if (mUnreadMsgs == 0) ++mUnreadMsgs;
00826   }
00827 
00828   ::chdir(path_buffer);
00829 }
00830 
00831 int KMFolderMaildir::createIndexFromContents()
00832 {
00833   mUnreadMsgs = 0;
00834 
00835   mMsgList.clear(true);
00836   mMsgList.reset(INIT_MSGS);
00837 
00838   mChanged = false;
00839 
00840   // first, we make sure that all the directories are here as they
00841   // should be
00842   QFileInfo dirinfo;
00843 
00844   dirinfo.setFile(location() + "/new");
00845   if (!dirinfo.exists() || !dirinfo.isDir())
00846   {
00847     kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl;
00848     return 1;
00849   }
00850   QDir newDir(location() + "/new");
00851   newDir.setFilter(QDir::Files);
00852 
00853   dirinfo.setFile(location() + "/cur");
00854   if (!dirinfo.exists() || !dirinfo.isDir())
00855   {
00856     kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl;
00857     return 1;
00858   }
00859   QDir curDir(location() + "/cur");
00860   curDir.setFilter(QDir::Files);
00861 
00862   // then, we look for all the 'cur' files
00863   const QFileInfoList *list = curDir.entryInfoList();
00864   QFileInfoListIterator it(*list);
00865   QFileInfo *fi;
00866 
00867   while ((fi = it.current()))
00868   {
00869     readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead);
00870     ++it;
00871   }
00872 
00873   // then, we look for all the 'new' files
00874   list = newDir.entryInfoList();
00875   it = *list;
00876 
00877   while ((fi=it.current()))
00878   {
00879     readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew);
00880     ++it;
00881   }
00882 
00883   if ( autoCreateIndex() ) {
00884     emit statusMsg(i18n("Writing index file"));
00885     writeIndex();
00886   }
00887   else mHeaderOffset = 0;
00888 
00889   correctUnreadMsgsCount();
00890 
00891   if (kmkernel->outboxFolder() == folder() && count() > 0)
00892     KMessageBox::information(0, i18n("Your outbox contains messages which were "
00893     "most-likely not created by KMail;\nplease remove them from there if you "
00894     "do not want KMail to send them."));
00895 
00896   needsCompact = true;
00897 
00898   invalidateFolder();
00899   return 0;
00900 }
00901 
00902 KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus()
00903 {
00904   QFileInfo new_info(location() + "/new");
00905   QFileInfo cur_info(location() + "/cur");
00906   QFileInfo index_info(indexLocation());
00907 
00908   if (!index_info.exists())
00909     return KMFolderIndex::IndexMissing;
00910 
00911   // Check whether the directories are more than 5 seconds newer than the index
00912   // file. The 5 seconds are added to reduce the number of false alerts due
00913   // to slightly out of sync clocks of the NFS server and the local machine.
00914   return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) ||
00915           (cur_info.lastModified() > index_info.lastModified().addSecs(5)))
00916          ? KMFolderIndex::IndexTooOld
00917          : KMFolderIndex::IndexOk;
00918 }
00919 
00920 //-----------------------------------------------------------------------------
00921 void KMFolderMaildir::removeMsg(int idx, bool)
00922 {
00923   KMMsgBase* msg = mMsgList[idx];
00924   if (!msg || !msg->fileName()) return;
00925 
00926   removeFile(msg->fileName());
00927 
00928   KMFolderIndex::removeMsg(idx);
00929 }
00930 
00931 //-----------------------------------------------------------------------------
00932 KMMessage* KMFolderMaildir::take(int idx)
00933 {
00934   // first, we do the high-level stuff.. then delete later
00935   KMMessage *msg = KMFolderIndex::take(idx);
00936 
00937   if (!msg || !msg->fileName()) {
00938     return 0;
00939   }
00940 
00941   if ( removeFile(msg->fileName()) ) {
00942     return msg;
00943   } else {
00944     return 0;
00945   }
00946 }
00947 
00948 // static
00949 bool KMFolderMaildir::removeFile( const QString & folderPath,
00950                                   const QString & filename )
00951 {
00952   // we need to look in both 'new' and 'cur' since it's possible to
00953   // delete a message before the folder is compacted. Since the file
00954   // naming and moving is done in ::compact, we can't assume any
00955   // location at this point.
00956   QCString abs_file( QFile::encodeName( folderPath + "/cur/" + filename ) );
00957   if ( ::unlink( abs_file ) == 0 )
00958     return true;
00959 
00960   if ( errno == ENOENT ) { // doesn't exist
00961     abs_file = QFile::encodeName( folderPath + "/new/" + filename );
00962     if ( ::unlink( abs_file ) == 0 )
00963       return true;
00964   }
00965 
00966   kdDebug(5006) << "Can't delete " << abs_file << " " << perror << endl;
00967   return false;
00968 }
00969 
00970 bool KMFolderMaildir::removeFile( const QString & filename )
00971 {
00972   return removeFile( location(), filename );
00973 }
00974 
00975 #include <sys/types.h>
00976 #include <dirent.h>
00977 static bool removeDirAndContentsRecursively( const QString & path )
00978 {
00979   bool success = true;
00980 
00981   QDir d;
00982   d.setPath( path );
00983   d.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks );
00984 
00985   const QFileInfoList *list = d.entryInfoList();
00986   QFileInfoListIterator it( *list );
00987   QFileInfo *fi;
00988 
00989   while ( (fi = it.current()) != 0 ) {
00990     if( fi->isDir() ) {
00991       if ( fi->fileName() != "." && fi->fileName() != ".." )
00992         success = success && removeDirAndContentsRecursively( fi->absFilePath() );
00993     } else {
00994       success = success && d.remove( fi->absFilePath() );
00995     }
00996     ++it;
00997   }
00998 
00999   if ( success ) {
01000     success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
01001   }
01002   return success;
01003 }
01004 
01005 //-----------------------------------------------------------------------------
01006 int KMFolderMaildir::removeContents()
01007 {
01008   // NOTE: Don' use KIO::netaccess, it has reentrancy problems and multiple
01009   // mailchecks going on trigger them, when removing dirs
01010   if ( !removeDirAndContentsRecursively( location() + "/new/" ) ) return 1;
01011   if ( !removeDirAndContentsRecursively( location() + "/cur/" ) ) return 1;
01012   if ( !removeDirAndContentsRecursively( location() + "/tmp/" ) ) return 1;
01013   /* The subdirs are removed now. Check if there is anything else in the dir
01014    * and only if not delete the dir itself. The user could have data stored
01015    * that would otherwise be deleted. */
01016   QDir dir(location());
01017   if ( dir.count() == 2 ) { // only . and ..
01018     if ( !removeDirAndContentsRecursively( location() ), 0 ) return 1;
01019   }
01020   return 0;
01021 }
01022 
01023 static QRegExp *suffix_regex = 0;
01024 static KStaticDeleter<QRegExp> suffix_regex_sd;
01025 
01026 //-----------------------------------------------------------------------------
01027 // static
01028 QString KMFolderMaildir::constructValidFileName( const QString & filename,
01029                                                  KMMsgStatus status )
01030 {
01031   QString aFileName( filename );
01032 
01033   if (aFileName.isEmpty())
01034   {
01035     aFileName.sprintf("%ld.%d.", (long)time(0), getpid());
01036     aFileName += KApplication::randomString(5);
01037   }
01038 
01039   if (!suffix_regex)
01040       suffix_regex_sd.setObject(suffix_regex, new QRegExp(":2,?R?S?$"));
01041 
01042   aFileName.truncate(aFileName.findRev(*suffix_regex));
01043 
01044   // only add status suffix if the message is neither new nor unread
01045   if (! ((status & KMMsgStatusNew) || (status & KMMsgStatusUnread)) )
01046   {
01047     QString suffix( ":2," );
01048     if (status & KMMsgStatusReplied)
01049       suffix += "RS";
01050     else
01051       suffix += "S";
01052     aFileName += suffix;
01053   }
01054 
01055   return aFileName;
01056 }
01057 
01058 //-----------------------------------------------------------------------------
01059 QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, KMMsgInfo *mi)
01060 {
01061   QString filename(mi->fileName());
01062   QString ret(moveInternal(oldLoc, newLoc, filename, mi->status()));
01063 
01064   if (filename != mi->fileName())
01065     mi->setFileName(filename);
01066 
01067   return ret;
01068 }
01069 
01070 //-----------------------------------------------------------------------------
01071 QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, KMMsgStatus status)
01072 {
01073   QString dest(newLoc);
01074   // make sure that our destination filename doesn't already exist
01075   while (QFile::exists(dest))
01076   {
01077     aFileName = constructValidFileName( QString(), status );
01078 
01079     QFileInfo fi(dest);
01080     dest = fi.dirPath(true) + "/" + aFileName;
01081     setDirty( true );
01082   }
01083 
01084   QDir d;
01085   if (d.rename(oldLoc, dest) == false)
01086     return QString::null;
01087   else
01088     return dest;
01089 }
01090 
01091 //-----------------------------------------------------------------------------
01092 void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus,
01093   const KMMsgStatus newStatus, int idx)
01094 {
01095   // if the status of any message changes, then we need to compact
01096   needsCompact = true;
01097 
01098   KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx);
01099 }
01100 
01101 /*virtual*/
01102 Q_INT64 KMFolderMaildir::doFolderSize() const
01103 {
01104   if ( mCurrentlyCheckingFolderSize )
01105   {
01106     return -1;
01107   }
01108   mCurrentlyCheckingFolderSize = true;
01109 
01110   KFileItemList list;
01111   KFileItem *item = 0;
01112   item = new KFileItem( S_IFDIR, -1, location() + "/cur" );
01113   list.append( item );
01114   item = new KFileItem( S_IFDIR, -1, location() + "/new" );
01115   list.append( item );
01116   item = new KFileItem( S_IFDIR, -1, location() + "/tmp" );
01117   list.append( item );
01118   s_DirSizeJobQueue.append(
01119     qMakePair( QGuardedPtr<const KMFolderMaildir>( this ), list ) );
01120 
01121   // if there's only one entry in the queue then we can start
01122   // a dirSizeJob right away
01123   if ( s_DirSizeJobQueue.size() == 1 )
01124   {
01125     //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
01126     //  << location() << endl;
01127     KDirSize* job = KDirSize::dirSizeJob( list );
01128     connect( job, SIGNAL( result( KIO::Job* ) ),
01129              this, SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
01130   }
01131 
01132   return -1;
01133 }
01134 
01135 void KMFolderMaildir::slotDirSizeJobResult( KIO::Job* job )
01136 {
01137     mCurrentlyCheckingFolderSize = false;
01138     KDirSize * dirsize = dynamic_cast<KDirSize*>( job );
01139     if ( dirsize && ! dirsize->error() )
01140     {
01141       mSize = dirsize->totalSize();
01142       //kdDebug(5006) << k_funcinfo << "dirSizeJob completed. Folder "
01143       //  << location() << " has size " << mSize << endl;
01144       emit folderSizeChanged();
01145     }
01146     // remove the completed job from the queue
01147     s_DirSizeJobQueue.pop_front();
01148 
01149     // process the next entry in the queue
01150     while ( s_DirSizeJobQueue.size() > 0 )
01151     {
01152       DirSizeJobQueueEntry entry = s_DirSizeJobQueue.first();
01153       // check whether the entry is valid, i.e. whether the folder still exists
01154       if ( entry.first )
01155       {
01156         // start the next dirSizeJob
01157         //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
01158         //  << entry.first->location() << endl;
01159         KDirSize* job = KDirSize::dirSizeJob( entry.second );
01160         connect( job, SIGNAL( result( KIO::Job* ) ),
01161                  entry.first, SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
01162         break;
01163       }
01164       else
01165       {
01166         // remove the invalid entry from the queue
01167         s_DirSizeJobQueue.pop_front();
01168       }
01169     }
01170 }
01171 
01172 #include "kmfoldermaildir.moc"