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       if (!readIndex())
00139         rc = createIndexFromContents();
00140   }
00141   else
00142   {
00143     mAutoCreateIndex = false;
00144     rc = createIndexFromContents();
00145   }
00146 
00147   mChanged = false;
00148 
00149   if (mIndexStream)
00150     fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00151 
00152   return rc;
00153 }
00154 
00155 
00156 //-----------------------------------------------------------------------------
00157 int KMFolderMaildir::createMaildirFolders( const QString & folderPath )
00158 {
00159   // Make sure that neither a new, cur or tmp subfolder exists already.
00160   QFileInfo dirinfo;
00161   dirinfo.setFile( folderPath + "/new" );
00162   if ( dirinfo.exists() ) return EEXIST;
00163   dirinfo.setFile( folderPath + "/cur" );
00164   if ( dirinfo.exists() ) return EEXIST;
00165   dirinfo.setFile( folderPath + "/tmp" );
00166   if ( dirinfo.exists() ) return EEXIST;
00167 
00168   // create the maildir directory structure
00169   if ( ::mkdir( QFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
00170     kdDebug(5006) << "Could not create folder " << folderPath << endl;
00171     return errno;
00172   }
00173   if ( ::mkdir( QFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
00174     kdDebug(5006) << "Could not create folder " << folderPath << "/new" << endl;
00175     return errno;
00176   }
00177   if ( ::mkdir( QFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
00178     kdDebug(5006) << "Could not create folder " << folderPath << "/cur" << endl;
00179     return errno;
00180   }
00181   if ( ::mkdir( QFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) {
00182     kdDebug(5006) << "Could not create folder " << folderPath << "/tmp" << endl;
00183     return errno;
00184   }
00185 
00186   return 0; // no error
00187 }
00188 
00189 //-----------------------------------------------------------------------------
00190 int KMFolderMaildir::create()
00191 {
00192   int rc;
00193   int old_umask;
00194 
00195   assert(!folder()->name().isEmpty());
00196   assert(mOpenCount == 0);
00197 
00198   rc = createMaildirFolders( location() );
00199   if ( rc != 0 )
00200     return rc;
00201 
00202   // FIXME no path == no index? - till
00203   if (!folder()->path().isEmpty())
00204   {
00205     old_umask = umask(077);
00206     mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW
00207     updateIndexStreamPtr(true);
00208     umask(old_umask);
00209 
00210     if (!mIndexStream) return errno;
00211     fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00212   }
00213   else
00214   {
00215     mAutoCreateIndex = false;
00216   }
00217 
00218   mOpenCount++;
00219   mChanged = false;
00220 
00221   rc = writeIndex();
00222   return rc;
00223 }
00224 
00225 
00226 //-----------------------------------------------------------------------------
00227 void KMFolderMaildir::reallyDoClose(const char* owner)
00228 {
00229   Q_UNUSED( owner );
00230   if (mAutoCreateIndex)
00231   {
00232       updateIndex();
00233       writeConfig();
00234   }
00235 
00236   mMsgList.clear(true);
00237 
00238   if (mIndexStream) {
00239     fclose(mIndexStream);
00240     updateIndexStreamPtr(true);
00241   }
00242 
00243   mOpenCount   = 0;
00244   mIndexStream = 0;
00245   mUnreadMsgs  = -1;
00246 
00247   mMsgList.reset(INIT_MSGS);
00248 }
00249 
00250 //-----------------------------------------------------------------------------
00251 void KMFolderMaildir::sync()
00252 {
00253   if (mOpenCount > 0)
00254     if (!mIndexStream || fsync(fileno(mIndexStream))) {
00255     kmkernel->emergencyExit( i18n("Could not sync maildir folder.") );
00256     }
00257 }
00258 
00259 //-----------------------------------------------------------------------------
00260 int KMFolderMaildir::expungeContents()
00261 {
00262   // nuke all messages in this folder now
00263   QDir d(location() + "/new");
00264   // d.setFilter(QDir::Files); coolo: QFile::remove returns false for non-files
00265   QStringList files(d.entryList());
00266   QStringList::ConstIterator it(files.begin());
00267   for ( ; it != files.end(); ++it)
00268     QFile::remove(d.filePath(*it));
00269 
00270   d.setPath(location() + "/cur");
00271   files = d.entryList();
00272   for (it = files.begin(); it != files.end(); ++it)
00273     QFile::remove(d.filePath(*it));
00274 
00275   return 0;
00276 }
00277 
00278 int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const QStringList& entryList, bool& done )
00279 {
00280   QString subdirNew(location() + "/new/");
00281   QString subdirCur(location() + "/cur/");
00282 
00283   unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
00284                            QMIN( mMsgList.count(), startIndex + nbMessages );
00285   //kdDebug(5006) << "KMFolderMaildir: compacting from " << startIndex << " to " << stopIndex << endl;
00286   for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
00287     KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
00288     if (!mi)
00289       continue;
00290 
00291     QString filename(mi->fileName());
00292     if (filename.isEmpty())
00293       continue;
00294 
00295     // first, make sure this isn't in the 'new' subdir
00296     if ( entryList.contains( filename ) )
00297       moveInternal(subdirNew + filename, subdirCur + filename, mi);
00298 
00299     // construct a valid filename.  if it's already valid, then
00300     // nothing happens
00301     filename = constructValidFileName( filename, mi->status() );
00302 
00303     // if the name changed, then we need to update the actual filename
00304     if (filename != mi->fileName())
00305     {
00306       moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi);
00307       mi->setFileName(filename);
00308       setDirty( true );
00309     }
00310 
00311 #if 0
00312     // we can't have any New messages at this point
00313     if (mi->isNew())
00314     {
00315       mi->setStatus(KMMsgStatusUnread);
00316       setDirty( true );
00317     }
00318 #endif
00319   }
00320   done = ( stopIndex == mMsgList.count() );
00321   return 0;
00322 }
00323 
00324 //-----------------------------------------------------------------------------
00325 int KMFolderMaildir::compact( bool silent )
00326 {
00327   KMail::MaildirCompactionJob* job = new KMail::MaildirCompactionJob( folder(), true /*immediate*/ );
00328   int rc = job->executeNow( silent );
00329   // Note that job autodeletes itself.
00330   return rc;
00331 }
00332 
00333 //-------------------------------------------------------------
00334 FolderJob*
00335 KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
00336                               KMFolder *folder, QString, const AttachmentStrategy* ) const
00337 {
00338   MaildirJob *job = new MaildirJob( msg, jt, folder );
00339   job->setParentFolder( this );
00340   return job;
00341 }
00342 
00343 //-------------------------------------------------------------
00344 FolderJob*
00345 KMFolderMaildir::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
00346                               FolderJob::JobType jt, KMFolder *folder ) const
00347 {
00348   MaildirJob *job = new MaildirJob( msgList, sets, jt, folder );
00349   job->setParentFolder( this );
00350   return job;
00351 }
00352 
00353 //-------------------------------------------------------------
00354 int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return)
00355 {
00356   if (!canAddMsgNow(aMsg, index_return)) {
00357     //kdDebug(5006) << "***DEBUG KMFolderMaildir:addMsg2() cant add now" << endl;
00358     return 0;
00359   }
00360 
00361   //kdDebug(5006) << "***DEBUG KMFolderMaildir:addMsg() " << endl;
00362   return addMsgInternal( aMsg, index_return );
00363 }
00364 
00365 //-------------------------------------------------------------
00366 int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return,
00367                                      bool stripUid )
00368 {
00369   //kdDebug(5006) << "****DEBUG KMFolderMaildir:addMsgInternal() " << endl;
00370 
00371 /*
00372 QFile fileD0( "testdat_xx-kmfoldermaildir-0" );
00373 if( fileD0.open( IO_WriteOnly ) ) {
00374     QDataStream ds( &fileD0 );
00375     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00376     fileD0.close();  // If data is 0 we just create a zero length file.
00377 }
00378 */
00379   long len;
00380   unsigned long size;
00381   KMFolder* msgParent;
00382   QCString msgText;
00383   int idx(-1);
00384   int rc;
00385 
00386   // take message out of the folder it is currently in, if any
00387   msgParent = aMsg->parent();
00388   if (msgParent)
00389   {
00390     if (msgParent==folder() && !kmkernel->folderIsDraftOrOutbox(folder()))
00391         return 0;
00392 
00393     idx = msgParent->find(aMsg);
00394     msgParent->getMsg( idx );
00395   }
00396 
00397   aMsg->setStatusFields();
00398   if (aMsg->headerField("Content-Type").isEmpty())  // This might be added by
00399     aMsg->removeHeaderField("Content-Type");        // the line above
00400 
00401 
00402   const QString uidHeader = aMsg->headerField( "X-UID" );
00403   if ( !uidHeader.isEmpty() && stripUid )
00404     aMsg->removeHeaderField( "X-UID" );
00405 
00406   msgText = aMsg->asString(); // TODO use asDwString instead
00407   len = msgText.length();
00408 
00409   // Re-add the uid so that the take can make use of it, in case the
00410   // message is currently in an imap folder
00411   if ( !uidHeader.isEmpty() && stripUid )
00412     aMsg->setHeaderField( "X-UID", uidHeader );
00413 
00414   if (len <= 0)
00415   {
00416     kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
00417     return 0;
00418   }
00419 
00420   // make sure the filename has the correct extension
00421   QString filename = constructValidFileName( aMsg->fileName(), aMsg->status() );
00422 
00423   QString tmp_file(location() + "/tmp/");
00424   tmp_file += filename;
00425 
00426   if (!KPIM::kCStringToFile(msgText, tmp_file, false, false, false))
00427     kmkernel->emergencyExit( i18n("Message could not be added to the folder, possibly disk space is low.") );
00428 
00429   QFile file(tmp_file);
00430   size = msgText.length();
00431 
00432   KMFolderOpener openThis(folder(), "maildir");
00433   rc = openThis.openResult();
00434   if (rc)
00435   {
00436     kdDebug(5006) << "KMFolderMaildir::addMsg-open: " << rc << " of folder: " << label() << endl;
00437     return rc;
00438   }
00439 
00440   // now move the file to the correct location
00441   QString new_loc(location() + "/cur/");
00442   new_loc += filename;
00443   if (moveInternal(tmp_file, new_loc, filename, aMsg->status()).isNull())
00444   {
00445     file.remove();
00446     return -1;
00447   }
00448 
00449   if (msgParent && idx >= 0)
00450     msgParent->take(idx);
00451 
00452   // just to be sure it does not end up in the index
00453   if ( stripUid ) aMsg->setUID( 0 );
00454 
00455   if (filename != aMsg->fileName())
00456     aMsg->setFileName(filename);
00457 
00458   if (aMsg->isUnread() || aMsg->isNew() || folder() == kmkernel->outboxFolder())
00459   {
00460     if (mUnreadMsgs == -1)
00461       mUnreadMsgs = 1;
00462     else
00463       ++mUnreadMsgs;
00464     if ( !mQuiet ) {
00465       kdDebug( 5006 ) << "FolderStorage::msgStatusChanged" << endl;
00466       emit numUnreadMsgsChanged( folder() );
00467     }else{
00468       if ( !mEmitChangedTimer->isActive() ) {
00469 //        kdDebug( 5006 )<< "QuietTimer started" << endl;
00470         mEmitChangedTimer->start( 3000 );
00471       }
00472       mChanged = true;
00473     }
00474   }
00475   ++mTotalMsgs;
00476   mSize = -1;
00477 
00478   if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) {
00479     aMsg->updateAttachmentState();
00480   }
00481   if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) {
00482     aMsg->updateInvitationState();
00483   }
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       */
00535       return error;
00536     }
00537   }
00538 
00539   if (index_return)
00540     *index_return = idx;
00541 
00542   emitMsgAddedSignals(idx);
00543   needsCompact = true;
00544 
00545 /*
00546 QFile fileD1( "testdat_xx-kmfoldermaildir-1" );
00547 if( fileD1.open( IO_WriteOnly ) ) {
00548     QDataStream ds( &fileD1 );
00549     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00550     fileD1.close();  // If data is 0 we just create a zero length file.
00551 }
00552 */
00553   return 0;
00554 }
00555 
00556 KMMessage* KMFolderMaildir::readMsg(int idx)
00557 {
00558   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00559   KMMessage *msg = new KMMessage(*mi);
00560   msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
00561   mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
00562   msg->setComplete( true );
00563   msg->fromDwString(getDwString(idx));
00564   return msg;
00565 }
00566 
00567 DwString KMFolderMaildir::getDwString(int idx)
00568 {
00569   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00570   QString abs_file(location() + "/cur/");
00571   abs_file += mi->fileName();
00572   QFileInfo fi( abs_file );
00573 
00574   if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0)
00575   {
00576     FILE* stream = fopen(QFile::encodeName(abs_file), "r+");
00577     if (stream) {
00578       size_t msgSize = fi.size();
00579       char* msgText = new char[ msgSize + 1 ];
00580       fread(msgText, msgSize, 1, stream);
00581       fclose( stream );
00582       msgText[msgSize] = '\0';
00583       size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize );
00584       DwString str;
00585       // the DwString takes possession of msgText, so we must not delete it
00586       str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
00587       return str;
00588     }
00589   }
00590   kdDebug(5006) << "Could not open file r+ " << abs_file << endl;
00591   return DwString();
00592 }
00593 
00594 
00595 void KMFolderMaildir::readFileHeaderIntern(const QString& dir, const QString& file, KMMsgStatus status)
00596 {
00597   // we keep our current directory to restore it later
00598   char path_buffer[PATH_MAX];
00599   if(!::getcwd(path_buffer, PATH_MAX - 1))
00600     return;
00601 
00602   ::chdir(QFile::encodeName(dir));
00603 
00604   // messages in the 'cur' directory are Read by default.. but may
00605   // actually be some other state (but not New)
00606   if (status == KMMsgStatusRead)
00607   {
00608     if (file.find(":2,") == -1)
00609       status = KMMsgStatusUnread;
00610     else if (file.right(5) == ":2,RS")
00611       status |= KMMsgStatusReplied;
00612   }
00613 
00614   // open the file and get a pointer to it
00615   QFile f(file);
00616   if ( f.open( IO_ReadOnly ) == false ) {
00617     kdWarning(5006) << "The file '" << QFile::encodeName(dir) << "/" << file
00618                     << "' could not be opened for reading the message. "
00619                        "Please check ownership and permissions."
00620                     << endl;
00621     return;
00622   }
00623 
00624   char line[MAX_LINE];
00625   bool atEof    = false;
00626   bool inHeader = true;
00627   QCString *lastStr = 0;
00628 
00629   QCString dateStr, fromStr, toStr, subjStr;
00630   QCString xmarkStr, replyToIdStr, msgIdStr, referencesStr;
00631   QCString statusStr, replyToAuxIdStr, uidStr;
00632   QCString contentTypeStr, charset;
00633 
00634   // iterate through this file until done
00635   while (!atEof)
00636   {
00637     // if the end of the file has been reached or if there was an error
00638     if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) )
00639       atEof = true;
00640 
00641     // are we done with this file?  if so, compile our info and store
00642     // it in a KMMsgInfo object
00643     if (atEof || !inHeader)
00644     {
00645       msgIdStr = msgIdStr.stripWhiteSpace();
00646       if( !msgIdStr.isEmpty() ) {
00647         int rightAngle;
00648         rightAngle = msgIdStr.find( '>' );
00649         if( rightAngle != -1 )
00650           msgIdStr.truncate( rightAngle + 1 );
00651       }
00652 
00653       replyToIdStr = replyToIdStr.stripWhiteSpace();
00654       if( !replyToIdStr.isEmpty() ) {
00655         int rightAngle;
00656         rightAngle = replyToIdStr.find( '>' );
00657         if( rightAngle != -1 )
00658           replyToIdStr.truncate( rightAngle + 1 );
00659       }
00660 
00661       referencesStr = referencesStr.stripWhiteSpace();
00662       if( !referencesStr.isEmpty() ) {
00663         int leftAngle, rightAngle;
00664         leftAngle = referencesStr.findRev( '<' );
00665         if( ( leftAngle != -1 )
00666             && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
00667           // use the last reference, instead of missing In-Reply-To
00668           replyToIdStr = referencesStr.mid( leftAngle );
00669         }
00670 
00671         // find second last reference
00672         leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
00673         if( leftAngle != -1 )
00674           referencesStr = referencesStr.mid( leftAngle );
00675         rightAngle = referencesStr.findRev( '>' );
00676         if( rightAngle != -1 )
00677           referencesStr.truncate( rightAngle + 1 );
00678 
00679         // Store the second to last reference in the replyToAuxIdStr
00680         // It is a good candidate for threading the message below if the
00681         // message In-Reply-To points to is not kept in this folder,
00682         // but e.g. in an Outbox
00683         replyToAuxIdStr = referencesStr;
00684         rightAngle = referencesStr.find( '>' );
00685         if( rightAngle != -1 )
00686           replyToAuxIdStr.truncate( rightAngle + 1 );
00687       }
00688 
00689       statusStr = statusStr.stripWhiteSpace();
00690       if (!statusStr.isEmpty())
00691       {
00692         // only handle those states not determined by the file suffix
00693         if (statusStr[0] == 'S')
00694           status |= KMMsgStatusSent;
00695         else if (statusStr[0] == 'F')
00696           status |= KMMsgStatusForwarded;
00697         else if (statusStr[0] == 'D')
00698           status |= KMMsgStatusDeleted;
00699         else if (statusStr[0] == 'Q')
00700           status |= KMMsgStatusQueued;
00701         else if (statusStr[0] == 'G')
00702           status |= KMMsgStatusFlag;
00703       }
00704 
00705       contentTypeStr = contentTypeStr.stripWhiteSpace();
00706       charset = "";
00707       if ( !contentTypeStr.isEmpty() )
00708       {
00709         int cidx = contentTypeStr.find( "charset=" );
00710         if ( cidx != -1 ) {
00711           charset = contentTypeStr.mid( cidx + 8 );
00712           if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
00713             charset = charset.mid( 1 );
00714           }
00715           cidx = 0;
00716           while ( (unsigned int) cidx < charset.length() ) {
00717             if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
00718                  charset[cidx] != '-' && charset[cidx] != '_' ) )
00719               break;
00720             ++cidx;
00721           }
00722           charset.truncate( cidx );
00723           // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
00724           //              charset << " from " << contentTypeStr << endl;
00725         }
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, charset, 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     else if (strncasecmp(line, "Content-Type:", 13) == 0)
00827     {
00828       contentTypeStr = QCString(line+13);
00829       lastStr = &contentTypeStr;
00830     }
00831 
00832   }
00833 
00834   if (status & KMMsgStatusNew || status & KMMsgStatusUnread ||
00835       (folder() == kmkernel->outboxFolder()))
00836   {
00837     mUnreadMsgs++;
00838    if (mUnreadMsgs == 0) ++mUnreadMsgs;
00839   }
00840 
00841   ::chdir(path_buffer);
00842 }
00843 
00844 int KMFolderMaildir::createIndexFromContents()
00845 {
00846   mUnreadMsgs = 0;
00847 
00848   mMsgList.clear(true);
00849   mMsgList.reset(INIT_MSGS);
00850 
00851   mChanged = false;
00852 
00853   // first, we make sure that all the directories are here as they
00854   // should be
00855   QFileInfo dirinfo;
00856 
00857   dirinfo.setFile(location() + "/new");
00858   if (!dirinfo.exists() || !dirinfo.isDir())
00859   {
00860     kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl;
00861     return 1;
00862   }
00863   QDir newDir(location() + "/new");
00864   newDir.setFilter(QDir::Files);
00865 
00866   dirinfo.setFile(location() + "/cur");
00867   if (!dirinfo.exists() || !dirinfo.isDir())
00868   {
00869     kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl;
00870     return 1;
00871   }
00872   QDir curDir(location() + "/cur");
00873   curDir.setFilter(QDir::Files);
00874 
00875   // then, we look for all the 'cur' files
00876   const QFileInfoList *list = curDir.entryInfoList();
00877   QFileInfoListIterator it(*list);
00878   QFileInfo *fi;
00879 
00880   while ((fi = it.current()))
00881   {
00882     readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead);
00883     ++it;
00884   }
00885 
00886   // then, we look for all the 'new' files
00887   list = newDir.entryInfoList();
00888   it = *list;
00889 
00890   while ((fi=it.current()))
00891   {
00892     readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew);
00893     ++it;
00894   }
00895 
00896   if ( autoCreateIndex() ) {
00897     emit statusMsg(i18n("Writing index file"));
00898     writeIndex();
00899   }
00900   else mHeaderOffset = 0;
00901 
00902   correctUnreadMsgsCount();
00903 
00904   if (kmkernel->outboxFolder() == folder() && count() > 0)
00905     KMessageBox::information(0, i18n("Your outbox contains messages which were "
00906     "most-likely not created by KMail;\nplease remove them from there if you "
00907     "do not want KMail to send them."));
00908 
00909   needsCompact = true;
00910   mCompactable = true;
00911 
00912   invalidateFolder();
00913   return 0;
00914 }
00915 
00916 KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus()
00917 {
00918   if ( !mCompactable )
00919     return KMFolderIndex::IndexCorrupt;
00920 
00921   QFileInfo new_info(location() + "/new");
00922   QFileInfo cur_info(location() + "/cur");
00923   QFileInfo index_info(indexLocation());
00924 
00925   if (!index_info.exists())
00926     return KMFolderIndex::IndexMissing;
00927 
00928   // Check whether the directories are more than 5 seconds newer than the index
00929   // file. The 5 seconds are added to reduce the number of false alerts due
00930   // to slightly out of sync clocks of the NFS server and the local machine.
00931   return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) ||
00932           (cur_info.lastModified() > index_info.lastModified().addSecs(5)))
00933          ? KMFolderIndex::IndexTooOld
00934          : KMFolderIndex::IndexOk;
00935 }
00936 
00937 //-----------------------------------------------------------------------------
00938 void KMFolderMaildir::removeMsg(int idx, bool)
00939 {
00940   KMMsgBase* msg = mMsgList[idx];
00941   if (!msg || !msg->fileName()) return;
00942 
00943   removeFile(msg->fileName());
00944 
00945   KMFolderIndex::removeMsg(idx);
00946 }
00947 
00948 //-----------------------------------------------------------------------------
00949 KMMessage* KMFolderMaildir::take(int idx)
00950 {
00951   // first, we do the high-level stuff.. then delete later
00952   KMMessage *msg = KMFolderIndex::take(idx);
00953 
00954   if (!msg || !msg->fileName()) {
00955     return 0;
00956   }
00957 
00958   if ( removeFile(msg->fileName()) ) {
00959     return msg;
00960   } else {
00961     //kdDebug() << "DEBUG KMFolderMaildir::take error removing " << idx << endl;
00962     return 0;
00963   }
00964 }
00965 
00966 // static
00967 bool KMFolderMaildir::removeFile( const QString & folderPath,
00968                                   const QString & filename )
00969 {
00970   // we need to look in both 'new' and 'cur' since it's possible to
00971   // delete a message before the folder is compacted. Since the file
00972   // naming and moving is done in ::compact, we can't assume any
00973   // location at this point.
00974   QCString abs_file( QFile::encodeName( folderPath + "/cur/" + filename ) );
00975   if ( ::unlink( abs_file ) == 0 )
00976     return true;
00977 
00978   if ( errno == ENOENT ) { // doesn't exist
00979     abs_file = QFile::encodeName( folderPath + "/new/" + filename );
00980     if ( ::unlink( abs_file ) == 0 )
00981       return true;
00982   }
00983 
00984   kdDebug(5006) << "Can't delete " << abs_file << " " << perror << endl;
00985   return false;
00986 }
00987 
00988 bool KMFolderMaildir::removeFile( const QString & filename )
00989 {
00990   return removeFile( location(), filename );
00991 }
00992 
00993 #include <sys/types.h>
00994 #include <dirent.h>
00995 static bool removeDirAndContentsRecursively( const QString & path )
00996 {
00997   bool success = true;
00998 
00999   QDir d;
01000   d.setPath( path );
01001   d.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks );
01002   if ( !d.exists() ) {
01003     return false;
01004   }
01005 
01006   const QFileInfoList *list = d.entryInfoList();
01007   QFileInfoListIterator it( *list );
01008   QFileInfo *fi;
01009 
01010   while ( (fi = it.current()) != 0 ) {
01011     if( fi->isDir() ) {
01012       if ( fi->fileName() != "." && fi->fileName() != ".." )
01013         success = success && removeDirAndContentsRecursively( fi->absFilePath() );
01014     } else {
01015       success = success && d.remove( fi->absFilePath() );
01016     }
01017     ++it;
01018   }
01019 
01020   if ( success ) {
01021     success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
01022   }
01023   return success;
01024 }
01025 
01026 //-----------------------------------------------------------------------------
01027 int KMFolderMaildir::removeContents()
01028 {
01029   // NOTE: Don' use KIO::netaccess, it has reentrancy problems and multiple
01030   // mailchecks going on trigger them, when removing dirs
01031   if ( !removeDirAndContentsRecursively( location() + "/new/" ) ) return 1;
01032   if ( !removeDirAndContentsRecursively( location() + "/cur/" ) ) return 1;
01033   if ( !removeDirAndContentsRecursively( location() + "/tmp/" ) ) return 1;
01034   /* The subdirs are removed now. Check if there is anything else in the dir
01035    * and only if not delete the dir itself. The user could have data stored
01036    * that would otherwise be deleted. */
01037   QDir dir(location());
01038   if ( dir.count() == 2 ) { // only . and ..
01039     if ( !removeDirAndContentsRecursively( location() ), 0 ) return 1;
01040   }
01041   return 0;
01042 }
01043 
01044 static QRegExp *suffix_regex = 0;
01045 static KStaticDeleter<QRegExp> suffix_regex_sd;
01046 
01047 //-----------------------------------------------------------------------------
01048 // static
01049 QString KMFolderMaildir::constructValidFileName( const QString & filename,
01050                                                  KMMsgStatus status )
01051 {
01052   QString aFileName( filename );
01053 
01054   if (aFileName.isEmpty())
01055   {
01056     aFileName.sprintf("%ld.%d.", (long)time(0), getpid());
01057     aFileName += KApplication::randomString(5);
01058   }
01059 
01060   if (!suffix_regex)
01061       suffix_regex_sd.setObject(suffix_regex, new QRegExp(":2,?R?S?$"));
01062 
01063   aFileName.truncate(aFileName.findRev(*suffix_regex));
01064 
01065   // only add status suffix if the message is neither new nor unread
01066   if (! ((status & KMMsgStatusNew) || (status & KMMsgStatusUnread)) )
01067   {
01068     QString suffix( ":2," );
01069     if (status & KMMsgStatusReplied)
01070       suffix += "RS";
01071     else
01072       suffix += "S";
01073     aFileName += suffix;
01074   }
01075 
01076   return aFileName;
01077 }
01078 
01079 //-----------------------------------------------------------------------------
01080 QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, KMMsgInfo *mi)
01081 {
01082   QString filename(mi->fileName());
01083   QString ret(moveInternal(oldLoc, newLoc, filename, mi->status()));
01084 
01085   if (filename != mi->fileName())
01086     mi->setFileName(filename);
01087 
01088   return ret;
01089 }
01090 
01091 //-----------------------------------------------------------------------------
01092 QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, KMMsgStatus status)
01093 {
01094   QString dest(newLoc);
01095   // make sure that our destination filename doesn't already exist
01096   while (QFile::exists(dest))
01097   {
01098     aFileName = constructValidFileName( QString(), status );
01099 
01100     QFileInfo fi(dest);
01101     dest = fi.dirPath(true) + "/" + aFileName;
01102     setDirty( true );
01103   }
01104 
01105   QDir d;
01106   if (d.rename(oldLoc, dest) == false)
01107     return QString::null;
01108   else
01109     return dest;
01110 }
01111 
01112 //-----------------------------------------------------------------------------
01113 void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus,
01114   const KMMsgStatus newStatus, int idx)
01115 {
01116   // if the status of any message changes, then we need to compact
01117   needsCompact = true;
01118 
01119   KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx);
01120 }
01121 
01122 /*virtual*/
01123 Q_INT64 KMFolderMaildir::doFolderSize() const
01124 {
01125   if ( mCurrentlyCheckingFolderSize )
01126   {
01127     return -1;
01128   }
01129   mCurrentlyCheckingFolderSize = true;
01130 
01131   KFileItemList list;
01132   KFileItem *item = 0;
01133   item = new KFileItem( S_IFDIR, -1, location() + "/cur" );
01134   list.append( item );
01135   item = new KFileItem( S_IFDIR, -1, location() + "/new" );
01136   list.append( item );
01137   item = new KFileItem( S_IFDIR, -1, location() + "/tmp" );
01138   list.append( item );
01139   s_DirSizeJobQueue.append(
01140     qMakePair( QGuardedPtr<const KMFolderMaildir>( this ), list ) );
01141 
01142   // if there's only one entry in the queue then we can start
01143   // a dirSizeJob right away
01144   if ( s_DirSizeJobQueue.size() == 1 )
01145   {
01146     //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
01147     //  << location() << endl;
01148     KDirSize* job = KDirSize::dirSizeJob( list );
01149     connect( job, SIGNAL( result( KIO::Job* ) ),
01150              this, SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
01151   }
01152 
01153   return -1;
01154 }
01155 
01156 void KMFolderMaildir::slotDirSizeJobResult( KIO::Job* job )
01157 {
01158     KDirSize * dirsize = dynamic_cast<KDirSize*>( job );
01159     if ( dirsize && ! dirsize->error() )
01160     {
01161       mSize = dirsize->totalSize();
01162       //kdDebug(5006) << k_funcinfo << "dirSizeJob completed. Folder "
01163       //  << location() << " has size " << mSize << endl;
01164       emit folderSizeChanged( folder() );
01165     }
01166     // remove the completed job from the queue
01167     s_DirSizeJobQueue.pop_front();
01168 
01169     // process the next entry in the queue
01170     while ( s_DirSizeJobQueue.size() > 0 )
01171     {
01172       DirSizeJobQueueEntry entry = s_DirSizeJobQueue.first();
01173       // check whether the entry is valid, i.e. whether the folder still exists
01174       if ( entry.first )
01175       {
01176         // start the next dirSizeJob
01177         //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
01178         //  << entry.first->location() << endl;
01179         KDirSize* job = KDirSize::dirSizeJob( entry.second );
01180         connect( job, SIGNAL( result( KIO::Job* ) ),
01181                  entry.first, SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
01182         return;
01183       }
01184       else
01185       {
01186         // remove the invalid entry from the queue
01187         s_DirSizeJobQueue.pop_front();
01188       }
01189     }
01190 
01191     // If we are here, folder size calculation is complete
01192     mCurrentlyCheckingFolderSize = false;
01193 }
01194 
01195 #include "kmfoldermaildir.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys