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