kmail

kmfoldermaildir.cpp

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