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