kmail Library API Documentation

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