kmail

kmfoldermaildir.cpp

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