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 <libtdepim/tdefileio.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 <tdeapplication.h> 00026 #include <kdebug.h> 00027 #include <tdelocale.h> 00028 #include <kstaticdeleter.h> 00029 #include <tdemessagebox.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 TDEIO::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 += TDEApplication::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( TDEIO::Job* ) ), 01136 this, TQT_SLOT( slotDirSizeJobResult( TDEIO::Job* ) ) ); 01137 } 01138 01139 return -1; 01140 } 01141 01142 void KMFolderMaildir::slotDirSizeJobResult( TDEIO::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( TDEIO::Job* ) ), 01168 entry.first, TQT_SLOT( slotDirSizeJobResult( TDEIO::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"