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 <tqdir.h>
00010 #include <tqregexp.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 TQValueList<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   TQString sBadFolderName;
00075   if (access(TQFile::encodeName(location()), R_OK | W_OK | X_OK) != 0) {
00076     sBadFolderName = location();
00077   } else if (access(TQFile::encodeName(location() + "/new"), R_OK | W_OK | X_OK) != 0) {
00078     sBadFolderName = location() + "/new";
00079   } else if (access(TQFile::encodeName(location() + "/cur"), R_OK | W_OK | X_OK) != 0) {
00080     sBadFolderName = location() + "/cur";
00081   } else if (access(TQFile::encodeName(location() + "/tmp"), R_OK | W_OK | X_OK) != 0) {
00082     sBadFolderName = location() + "/tmp";
00083   }
00084 
00085   if ( !sBadFolderName.isEmpty() ) {
00086     int nRetVal = TQFile::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       TQString str;
00123       mIndexStream = 0;
00124       str = i18n("Folder `%1' changed; recreating index.")
00125           .arg(name());
00126       emit statusMsg(str);
00127     } else {
00128       mIndexStream = fopen(TQFile::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 TQString & folderPath )
00156 {
00157   // Make sure that neither a new, cur or tmp subfolder exists already.
00158   TQFileInfo 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( TQFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
00168     kdDebug(5006) << "Could not create folder " << folderPath << endl;
00169     return errno;
00170   }
00171   if ( ::mkdir( TQFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
00172     kdDebug(5006) << "Could not create folder " << folderPath << "/new" << endl;
00173     return errno;
00174   }
00175   if ( ::mkdir( TQFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
00176     kdDebug(5006) << "Could not create folder " << folderPath << "/cur" << endl;
00177     return errno;
00178   }
00179   if ( ::mkdir( TQFile::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(TQFile::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   TQDir d(location() + "/new");
00262   // d.setFilter(TQDir::Files); coolo: TQFile::remove returns false for non-files
00263   TQStringList files(d.entryList());
00264   TQStringList::ConstIterator it(files.begin());
00265   for ( ; it != files.end(); ++it)
00266     TQFile::remove(d.filePath(*it));
00267 
00268   d.setPath(location() + "/cur");
00269   files = d.entryList();
00270   for (it = files.begin(); it != files.end(); ++it)
00271     TQFile::remove(d.filePath(*it));
00272 
00273   return 0;
00274 }
00275 
00276 int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const TQStringList& entryList, bool& done )
00277 {
00278   TQString subdirNew(location() + "/new/");
00279   TQString subdirCur(location() + "/cur/");
00280 
00281   unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
00282                            TQMIN( 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     TQString 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, TQString, 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( TQPtrList<KMMessage>& msgList, const TQString& 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 TQFile fileD0( "testdat_xx-kmfoldermaildir-0" );
00364 if( fileD0.open( IO_WriteOnly ) ) {
00365     TQDataStream 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   TQCString 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 TQString 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   TQString filename = constructValidFileName( aMsg->fileName(), aMsg->status() );
00413 
00414   TQString 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   TQFile 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   TQString 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 && aMsg->readyToShow() ) {
00470     aMsg->updateAttachmentState();
00471   }
00472   if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) {
00473     aMsg->updateInvitationState();
00474   }
00475 
00476   // store information about the position in the folder file in the message
00477   aMsg->setParent(folder());
00478   aMsg->setMsgSize(size);
00479   idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums );
00480   if (aMsg->getMsgSerNum() <= 0)
00481     aMsg->setMsgSerNum();
00482   else
00483     replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
00484 
00485   // write index entry if desired
00486   if (mAutoCreateIndex)
00487   {
00488     assert(mIndexStream != 0);
00489     clearerr(mIndexStream);
00490     fseek(mIndexStream, 0, SEEK_END);
00491     off_t revert = ftell(mIndexStream);
00492 
00493     int len;
00494     KMMsgBase * mb = &aMsg->toMsgBase();
00495     const uchar *buffer = mb->asIndexString(len);
00496     fwrite(&len,sizeof(len), 1, mIndexStream);
00497     mb->setIndexOffset( ftell(mIndexStream) );
00498     mb->setIndexLength( len );
00499     if(fwrite(buffer, len, 1, mIndexStream) != 1)
00500       kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
00501 
00502     fflush(mIndexStream);
00503     int error = ferror(mIndexStream);
00504 
00505     if ( mExportsSernums )
00506       error |= appendToFolderIdsFile( idx );
00507 
00508     if (error) {
00509       kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
00510       if (ftell(mIndexStream) > revert) {
00511     kdDebug(5006) << "Undoing changes" << endl;
00512     truncate( TQFile::encodeName(indexLocation()), revert );
00513       }
00514       kmkernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss."));
00515       // exit(1); // don't ever use exit(), use the above!
00516 
00517       /* This code may not be 100% reliable
00518       bool busy = kmkernel->kbp()->isBusy();
00519       if (busy) kmkernel->kbp()->idle();
00520       KMessageBox::sorry(0,
00521         i18n("Unable to add message to folder.\n"
00522          "(No space left on device or insufficient quota?)\n"
00523          "Free space and sufficient quota are required to continue safely."));
00524       if (busy) kmkernel->kbp()->busy();
00525       */
00526       return error;
00527     }
00528   }
00529 
00530   if (index_return)
00531     *index_return = idx;
00532 
00533   emitMsgAddedSignals(idx);
00534   needsCompact = true;
00535 
00536 /*
00537 TQFile fileD1( "testdat_xx-kmfoldermaildir-1" );
00538 if( fileD1.open( IO_WriteOnly ) ) {
00539     TQDataStream ds( &fileD1 );
00540     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00541     fileD1.close();  // If data is 0 we just create a zero length file.
00542 }
00543 */
00544   return 0;
00545 }
00546 
00547 KMMessage* KMFolderMaildir::readMsg(int idx)
00548 {
00549   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00550   KMMessage *msg = new KMMessage(*mi);
00551   msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
00552   mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
00553   msg->setComplete( true );
00554   msg->fromDwString(getDwString(idx));
00555   return msg;
00556 }
00557 
00558 DwString KMFolderMaildir::getDwString(int idx)
00559 {
00560   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00561   TQString abs_file(location() + "/cur/");
00562   abs_file += mi->fileName();
00563   TQFileInfo fi( abs_file );
00564 
00565   if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0)
00566   {
00567     FILE* stream = fopen(TQFile::encodeName(abs_file), "r+");
00568     if (stream) {
00569       size_t msgSize = fi.size();
00570       char* msgText = new char[ msgSize + 1 ];
00571       fread(msgText, msgSize, 1, stream);
00572       fclose( stream );
00573       msgText[msgSize] = '\0';
00574       size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize );
00575       DwString str;
00576       // the DwString takes possession of msgText, so we must not delete it
00577       str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
00578       return str;
00579     }
00580   }
00581   kdDebug(5006) << "Could not open file r+ " << abs_file << endl;
00582   return DwString();
00583 }
00584 
00585 
00586 void KMFolderMaildir::readFileHeaderIntern(const TQString& dir, const TQString& file, KMMsgStatus status)
00587 {
00588   // we keep our current directory to restore it later
00589   char path_buffer[PATH_MAX];
00590   if(!::getcwd(path_buffer, PATH_MAX - 1))
00591     return;
00592 
00593   ::chdir(TQFile::encodeName(dir));
00594 
00595   // messages in the 'cur' directory are Read by default.. but may
00596   // actually be some other state (but not New)
00597   if (status == KMMsgStatusRead)
00598   {
00599     if (file.find(":2,") == -1)
00600       status = KMMsgStatusUnread;
00601     else if (file.right(5) == ":2,RS")
00602       status |= KMMsgStatusReplied;
00603   }
00604 
00605   // open the file and get a pointer to it
00606   TQFile f(file);
00607   if ( f.open( IO_ReadOnly ) == false ) {
00608     kdWarning(5006) << "The file '" << TQString(TQFile::encodeName(dir)) << "/" << file
00609                     << "' could not be opened for reading the message. "
00610                        "Please check ownership and permissions."
00611                     << endl;
00612     return;
00613   }
00614 
00615   char line[MAX_LINE];
00616   bool atEof    = false;
00617   bool inHeader = true;
00618   TQCString *lastStr = 0;
00619 
00620   TQCString dateStr, fromStr, toStr, subjStr;
00621   TQCString xmarkStr, replyToIdStr, msgIdStr, referencesStr;
00622   TQCString statusStr, replyToAuxIdStr, uidStr;
00623   TQCString contentTypeStr, charset;
00624 
00625   // iterate through this file until done
00626   while (!atEof)
00627   {
00628     // if the end of the file has been reached or if there was an error
00629     if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) )
00630       atEof = true;
00631 
00632     // are we done with this file?  if so, compile our info and store
00633     // it in a KMMsgInfo object
00634     if (atEof || !inHeader)
00635     {
00636       msgIdStr = msgIdStr.stripWhiteSpace();
00637       if( !msgIdStr.isEmpty() ) {
00638         int rightAngle;
00639         rightAngle = msgIdStr.find( '>' );
00640         if( rightAngle != -1 )
00641           msgIdStr.truncate( rightAngle + 1 );
00642       }
00643 
00644       replyToIdStr = replyToIdStr.stripWhiteSpace();
00645       if( !replyToIdStr.isEmpty() ) {
00646         int rightAngle;
00647         rightAngle = replyToIdStr.find( '>' );
00648         if( rightAngle != -1 )
00649           replyToIdStr.truncate( rightAngle + 1 );
00650       }
00651 
00652       referencesStr = referencesStr.stripWhiteSpace();
00653       if( !referencesStr.isEmpty() ) {
00654         int leftAngle, rightAngle;
00655         leftAngle = referencesStr.findRev( '<' );
00656         if( ( leftAngle != -1 )
00657             && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
00658           // use the last reference, instead of missing In-Reply-To
00659           replyToIdStr = referencesStr.mid( leftAngle );
00660         }
00661 
00662         // find second last reference
00663         leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
00664         if( leftAngle != -1 )
00665           referencesStr = referencesStr.mid( leftAngle );
00666         rightAngle = referencesStr.findRev( '>' );
00667         if( rightAngle != -1 )
00668           referencesStr.truncate( rightAngle + 1 );
00669 
00670         // Store the second to last reference in the replyToAuxIdStr
00671         // It is a good candidate for threading the message below if the
00672         // message In-Reply-To points to is not kept in this folder,
00673         // but e.g. in an Outbox
00674         replyToAuxIdStr = referencesStr;
00675         rightAngle = referencesStr.find( '>' );
00676         if( rightAngle != -1 )
00677           replyToAuxIdStr.truncate( rightAngle + 1 );
00678       }
00679 
00680       statusStr = statusStr.stripWhiteSpace();
00681       if (!statusStr.isEmpty())
00682       {
00683         // only handle those states not determined by the file suffix
00684         if (statusStr[0] == 'S')
00685           status |= KMMsgStatusSent;
00686         else if (statusStr[0] == 'F')
00687           status |= KMMsgStatusForwarded;
00688         else if (statusStr[0] == 'D')
00689           status |= KMMsgStatusDeleted;
00690         else if (statusStr[0] == 'Q')
00691           status |= KMMsgStatusQueued;
00692         else if (statusStr[0] == 'G')
00693           status |= KMMsgStatusFlag;
00694       }
00695 
00696       contentTypeStr = contentTypeStr.stripWhiteSpace();
00697       charset = "";
00698       if ( !contentTypeStr.isEmpty() )
00699       {
00700         int cidx = contentTypeStr.find( "charset=" );
00701         if ( cidx != -1 ) {
00702           charset = contentTypeStr.mid( cidx + 8 );
00703           if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
00704             charset = charset.mid( 1 );
00705           }
00706           cidx = 0;
00707           while ( (unsigned int) cidx < charset.length() ) {
00708             if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
00709                  charset[cidx] != '-' && charset[cidx] != '_' ) )
00710               break;
00711             ++cidx;
00712           }
00713           charset.truncate( cidx );
00714           // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
00715           //              charset << " from " << contentTypeStr << endl;
00716         }
00717       }
00718 
00719       KMMsgInfo *mi = new KMMsgInfo(folder());
00720       mi->init( subjStr.stripWhiteSpace(),
00721                 fromStr.stripWhiteSpace(),
00722                 toStr.stripWhiteSpace(),
00723                 0, status,
00724                 xmarkStr.stripWhiteSpace(),
00725                 replyToIdStr, replyToAuxIdStr, msgIdStr,
00726                 file.local8Bit(),
00727                 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
00728                 KMMsgMDNStateUnknown, charset, f.size() );
00729 
00730       dateStr = dateStr.stripWhiteSpace();
00731       if (!dateStr.isEmpty())
00732         mi->setDate(dateStr.data());
00733       if ( !uidStr.isEmpty() )
00734          mi->setUID( uidStr.toULong() );
00735       mi->setDirty(false);
00736       mMsgList.append( mi, mExportsSernums );
00737 
00738       // if this is a New file and is in 'new', we move it to 'cur'
00739       if (status & KMMsgStatusNew)
00740       {
00741         TQString newDir(location() + "/new/");
00742         TQString curDir(location() + "/cur/");
00743         moveInternal(newDir + file, curDir + file, mi);
00744       }
00745 
00746       break;
00747     }
00748 
00749     // Is this a long header line?
00750     if (inHeader && ( line[0] == '\t' || line[0] == ' ' ) )
00751     {
00752       int i = 0;
00753       while (line[i] == '\t' || line[i] == ' ')
00754         i++;
00755       if (line[i] < ' ' && line[i] > 0)
00756         inHeader = false;
00757       else
00758         if (lastStr)
00759           *lastStr += line + i;
00760     }
00761     else
00762       lastStr = 0;
00763 
00764     if (inHeader && (line[0] == '\n' || line[0] == '\r'))
00765       inHeader = false;
00766     if (!inHeader)
00767       continue;
00768 
00769     if (strncasecmp(line, "Date:", 5) == 0)
00770     {
00771       dateStr = TQCString(line+5);
00772       lastStr = &dateStr;
00773     }
00774     else if (strncasecmp(line, "From:", 5) == 0)
00775     {
00776       fromStr = TQCString(line+5);
00777       lastStr = &fromStr;
00778     }
00779     else if (strncasecmp(line, "To:", 3) == 0)
00780     {
00781       toStr = TQCString(line+3);
00782       lastStr = &toStr;
00783     }
00784     else if (strncasecmp(line, "Subject:", 8) == 0)
00785     {
00786       subjStr = TQCString(line+8);
00787       lastStr = &subjStr;
00788     }
00789     else if (strncasecmp(line, "References:", 11) == 0)
00790     {
00791       referencesStr = TQCString(line+11);
00792       lastStr = &referencesStr;
00793     }
00794     else if (strncasecmp(line, "Message-Id:", 11) == 0)
00795     {
00796       msgIdStr = TQCString(line+11);
00797       lastStr = &msgIdStr;
00798     }
00799     else if (strncasecmp(line, "X-KMail-Mark:", 13) == 0)
00800     {
00801       xmarkStr = TQCString(line+13);
00802     }
00803     else if (strncasecmp(line, "X-Status:", 9) == 0)
00804     {
00805       statusStr = TQCString(line+9);
00806     }
00807     else if (strncasecmp(line, "In-Reply-To:", 12) == 0)
00808     {
00809       replyToIdStr = TQCString(line+12);
00810       lastStr = &replyToIdStr;
00811     }
00812     else if (strncasecmp(line, "X-UID:", 6) == 0)
00813     {
00814       uidStr = TQCString(line+6);
00815       lastStr = &uidStr;
00816     }
00817     else if (strncasecmp(line, "Content-Type:", 13) == 0)
00818     {
00819       contentTypeStr = TQCString(line+13);
00820       lastStr = &contentTypeStr;
00821     }
00822 
00823   }
00824 
00825   if (status & KMMsgStatusNew || status & KMMsgStatusUnread ||
00826       (folder() == kmkernel->outboxFolder()))
00827   {
00828     mUnreadMsgs++;
00829    if (mUnreadMsgs == 0) ++mUnreadMsgs;
00830   }
00831 
00832   ::chdir(path_buffer);
00833 }
00834 
00835 int KMFolderMaildir::createIndexFromContents()
00836 {
00837   mUnreadMsgs = 0;
00838 
00839   mMsgList.clear(true);
00840   mMsgList.reset(INIT_MSGS);
00841 
00842   mChanged = false;
00843 
00844   // first, we make sure that all the directories are here as they
00845   // should be
00846   TQFileInfo dirinfo;
00847 
00848   dirinfo.setFile(location() + "/new");
00849   if (!dirinfo.exists() || !dirinfo.isDir())
00850   {
00851     kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl;
00852     return 1;
00853   }
00854   TQDir newDir(location() + "/new");
00855   newDir.setFilter(TQDir::Files);
00856 
00857   dirinfo.setFile(location() + "/cur");
00858   if (!dirinfo.exists() || !dirinfo.isDir())
00859   {
00860     kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl;
00861     return 1;
00862   }
00863   TQDir curDir(location() + "/cur");
00864   curDir.setFilter(TQDir::Files);
00865 
00866   // then, we look for all the 'cur' files
00867   const TQFileInfoList *list = curDir.entryInfoList();
00868   TQFileInfoListIterator it(*list);
00869   TQFileInfo *fi;
00870 
00871   while ((fi = it.current()))
00872   {
00873     readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead);
00874     ++it;
00875   }
00876 
00877   // then, we look for all the 'new' files
00878   list = newDir.entryInfoList();
00879   it = *list;
00880 
00881   while ((fi=it.current()))
00882   {
00883     readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew);
00884     ++it;
00885   }
00886 
00887   if ( autoCreateIndex() ) {
00888     emit statusMsg(i18n("Writing index file"));
00889     writeIndex();
00890   }
00891   else mHeaderOffset = 0;
00892 
00893   correctUnreadMsgsCount();
00894 
00895   if (kmkernel->outboxFolder() == folder() && count() > 0)
00896     KMessageBox::information(0, i18n("Your outbox contains messages which were "
00897     "most-likely not created by KMail;\nplease remove them from there if you "
00898     "do not want KMail to send them."));
00899 
00900   needsCompact = true;
00901 
00902   invalidateFolder();
00903   return 0;
00904 }
00905 
00906 KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus()
00907 {
00908   if ( !mCompactable )
00909     return KMFolderIndex::IndexCorrupt;
00910 
00911   TQFileInfo new_info(location() + "/new");
00912   TQFileInfo cur_info(location() + "/cur");
00913   TQFileInfo index_info(indexLocation());
00914 
00915   if (!index_info.exists())
00916     return KMFolderIndex::IndexMissing;
00917 
00918   // Check whether the directories are more than 5 seconds newer than the index
00919   // file. The 5 seconds are added to reduce the number of false alerts due
00920   // to slightly out of sync clocks of the NFS server and the local machine.
00921   return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) ||
00922           (cur_info.lastModified() > index_info.lastModified().addSecs(5)))
00923          ? KMFolderIndex::IndexTooOld
00924          : KMFolderIndex::IndexOk;
00925 }
00926 
00927 //-----------------------------------------------------------------------------
00928 void KMFolderMaildir::removeMsg(int idx, bool)
00929 {
00930   KMMsgBase* msg = mMsgList[idx];
00931   if (!msg || !msg->fileName()) return;
00932 
00933   removeFile(msg->fileName());
00934 
00935   KMFolderIndex::removeMsg(idx);
00936 }
00937 
00938 //-----------------------------------------------------------------------------
00939 KMMessage* KMFolderMaildir::take(int idx)
00940 {
00941   // first, we do the high-level stuff.. then delete later
00942   KMMessage *msg = KMFolderIndex::take(idx);
00943 
00944   if (!msg || !msg->fileName()) {
00945     return 0;
00946   }
00947 
00948   if ( removeFile(msg->fileName()) ) {
00949     return msg;
00950   } else {
00951     return 0;
00952   }
00953 }
00954 
00955 // static
00956 bool KMFolderMaildir::removeFile( const TQString & folderPath,
00957                                   const TQString & filename )
00958 {
00959   // we need to look in both 'new' and 'cur' since it's possible to
00960   // delete a message before the folder is compacted. Since the file
00961   // naming and moving is done in ::compact, we can't assume any
00962   // location at this point.
00963   TQCString abs_file( TQFile::encodeName( folderPath + "/cur/" + filename ) );
00964   if ( ::unlink( abs_file ) == 0 )
00965     return true;
00966 
00967   if ( errno == ENOENT ) { // doesn't exist
00968     abs_file = TQFile::encodeName( folderPath + "/new/" + filename );
00969     if ( ::unlink( abs_file ) == 0 )
00970       return true;
00971   }
00972 
00973   kdDebug(5006) << "Can't delete " << abs_file << " " << perror << endl;
00974   return false;
00975 }
00976 
00977 bool KMFolderMaildir::removeFile( const TQString & filename )
00978 {
00979   return removeFile( location(), filename );
00980 }
00981 
00982 #include <sys/types.h>
00983 #include <dirent.h>
00984 static bool removeDirAndContentsRecursively( const TQString & path )
00985 {
00986   bool success = true;
00987 
00988   TQDir d;
00989   d.setPath( path );
00990   d.setFilter( TQDir::Files | TQDir::Dirs | TQDir::Hidden | TQDir::NoSymLinks );
00991 
00992   const TQFileInfoList *list = d.entryInfoList();
00993   TQFileInfoListIterator it( *list );
00994   TQFileInfo *fi;
00995 
00996   while ( (fi = it.current()) != 0 ) {
00997     if( fi->isDir() ) {
00998       if ( fi->fileName() != "." && fi->fileName() != ".." )
00999         success = success && removeDirAndContentsRecursively( fi->absFilePath() );
01000     } else {
01001       success = success && d.remove( fi->absFilePath() );
01002     }
01003     ++it;
01004   }
01005 
01006   if ( success ) {
01007     success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
01008   }
01009   return success;
01010 }
01011 
01012 //-----------------------------------------------------------------------------
01013 int KMFolderMaildir::removeContents()
01014 {
01015   // NOTE: Don' use KIO::netaccess, it has reentrancy problems and multiple
01016   // mailchecks going on trigger them, when removing dirs
01017   if ( !removeDirAndContentsRecursively( location() + "/new/" ) ) return 1;
01018   if ( !removeDirAndContentsRecursively( location() + "/cur/" ) ) return 1;
01019   if ( !removeDirAndContentsRecursively( location() + "/tmp/" ) ) return 1;
01020   /* The subdirs are removed now. Check if there is anything else in the dir
01021    * and only if not delete the dir itself. The user could have data stored
01022    * that would otherwise be deleted. */
01023   TQDir dir(location());
01024   if ( dir.count() == 2 ) { // only . and ..
01025     if ( !removeDirAndContentsRecursively( location() ), 0 ) return 1;
01026   }
01027   return 0;
01028 }
01029 
01030 static TQRegExp *suffix_regex = 0;
01031 static KStaticDeleter<TQRegExp> suffix_regex_sd;
01032 
01033 //-----------------------------------------------------------------------------
01034 // static
01035 TQString KMFolderMaildir::constructValidFileName( const TQString & filename,
01036                                                  KMMsgStatus status )
01037 {
01038   TQString aFileName( filename );
01039 
01040   if (aFileName.isEmpty())
01041   {
01042     aFileName.sprintf("%ld.%d.", (long)time(0), getpid());
01043     aFileName += KApplication::randomString(5);
01044   }
01045 
01046   if (!suffix_regex)
01047       suffix_regex_sd.setObject(suffix_regex, new TQRegExp(":2,?R?S?$"));
01048 
01049   aFileName.truncate(aFileName.findRev(*suffix_regex));
01050 
01051   // only add status suffix if the message is neither new nor unread
01052   if (! ((status & KMMsgStatusNew) || (status & KMMsgStatusUnread)) )
01053   {
01054     TQString suffix( ":2," );
01055     if (status & KMMsgStatusReplied)
01056       suffix += "RS";
01057     else
01058       suffix += "S";
01059     aFileName += suffix;
01060   }
01061 
01062   return aFileName;
01063 }
01064 
01065 //-----------------------------------------------------------------------------
01066 TQString KMFolderMaildir::moveInternal(const TQString& oldLoc, const TQString& newLoc, KMMsgInfo *mi)
01067 {
01068   TQString filename(mi->fileName());
01069   TQString ret(moveInternal(oldLoc, newLoc, filename, mi->status()));
01070 
01071   if (filename != mi->fileName())
01072     mi->setFileName(filename);
01073 
01074   return ret;
01075 }
01076 
01077 //-----------------------------------------------------------------------------
01078 TQString KMFolderMaildir::moveInternal(const TQString& oldLoc, const TQString& newLoc, TQString& aFileName, KMMsgStatus status)
01079 {
01080   TQString dest(newLoc);
01081   // make sure that our destination filename doesn't already exist
01082   while (TQFile::exists(dest))
01083   {
01084     aFileName = constructValidFileName( TQString(), status );
01085 
01086     TQFileInfo fi(dest);
01087     dest = fi.dirPath(true) + "/" + aFileName;
01088     setDirty( true );
01089   }
01090 
01091   TQDir d;
01092   if (d.rename(oldLoc, dest) == false)
01093     return TQString();
01094   else
01095     return dest;
01096 }
01097 
01098 //-----------------------------------------------------------------------------
01099 void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus,
01100   const KMMsgStatus newStatus, int idx)
01101 {
01102   // if the status of any message changes, then we need to compact
01103   needsCompact = true;
01104 
01105   KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx);
01106 }
01107 
01108 /*virtual*/
01109 TQ_INT64 KMFolderMaildir::doFolderSize() const
01110 {
01111   if ( mCurrentlyCheckingFolderSize )
01112   {
01113     return -1;
01114   }
01115   mCurrentlyCheckingFolderSize = true;
01116 
01117   KFileItemList list;
01118   KFileItem *item = 0;
01119   item = new KFileItem( S_IFDIR, -1, location() + "/cur" );
01120   list.append( item );
01121   item = new KFileItem( S_IFDIR, -1, location() + "/new" );
01122   list.append( item );
01123   item = new KFileItem( S_IFDIR, -1, location() + "/tmp" );
01124   list.append( item );
01125   s_DirSizeJobQueue.append(
01126     tqMakePair( TQGuardedPtr<const KMFolderMaildir>( this ), list ) );
01127 
01128   // if there's only one entry in the queue then we can start
01129   // a dirSizeJob right away
01130   if ( s_DirSizeJobQueue.size() == 1 )
01131   {
01132     //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
01133     //  << location() << endl;
01134     KDirSize* job = KDirSize::dirSizeJob( list );
01135     connect( job, TQT_SIGNAL( result( KIO::Job* ) ),
01136              this, TQT_SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
01137   }
01138 
01139   return -1;
01140 }
01141 
01142 void KMFolderMaildir::slotDirSizeJobResult( KIO::Job* job )
01143 {
01144     mCurrentlyCheckingFolderSize = false;
01145     KDirSize * dirsize = dynamic_cast<KDirSize*>( job );
01146     if ( dirsize && ! dirsize->error() )
01147     {
01148       mSize = dirsize->totalSize();
01149       //kdDebug(5006) << k_funcinfo << "dirSizeJob completed. Folder "
01150       //  << location() << " has size " << mSize << endl;
01151       emit folderSizeChanged();
01152     }
01153     // remove the completed job from the queue
01154     s_DirSizeJobQueue.pop_front();
01155 
01156     // process the next entry in the queue
01157     while ( s_DirSizeJobQueue.size() > 0 )
01158     {
01159       DirSizeJobQueueEntry entry = s_DirSizeJobQueue.first();
01160       // check whether the entry is valid, i.e. whether the folder still exists
01161       if ( entry.first )
01162       {
01163         // start the next dirSizeJob
01164         //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
01165         //  << entry.first->location() << endl;
01166         KDirSize* job = KDirSize::dirSizeJob( entry.second );
01167         connect( job, TQT_SIGNAL( result( KIO::Job* ) ),
01168                  entry.first, TQT_SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
01169         break;
01170       }
01171       else
01172       {
01173         // remove the invalid entry from the queue
01174         s_DirSizeJobQueue.pop_front();
01175       }
01176     }
01177 }
01178 
01179 #include "kmfoldermaildir.moc"