kmail Library API Documentation

kmfoldermbox.cpp

00001 // -*- c-basic-offset: 2 -*- 00002 // kmfoldermbox.cpp 00003 // Author: Stefan Taferner <taferner@alpin.or.at> 00004 00005 #include <config.h> 00006 #include <qfileinfo.h> 00007 #include <qregexp.h> 00008 00009 #include "kmfoldermbox.h" 00010 #include "kmfoldermgr.h" 00011 #include "kmfolder.h" 00012 #include "undostack.h" 00013 #include "kcursorsaver.h" 00014 #include "jobscheduler.h" 00015 #include "compactionjob.h" 00016 00017 #include <kdebug.h> 00018 #include <klocale.h> 00019 #include <kmessagebox.h> 00020 #include <knotifyclient.h> 00021 #include <kprocess.h> 00022 #include <kconfig.h> 00023 00024 #include <stdio.h> 00025 #include <errno.h> 00026 #include <assert.h> 00027 #include <unistd.h> 00028 00029 #ifdef HAVE_FCNTL_H 00030 #include <fcntl.h> 00031 #endif 00032 00033 #include <stdlib.h> 00034 #include <sys/types.h> 00035 #include <sys/stat.h> 00036 #include <sys/file.h> 00037 #include "broadcaststatus.h" 00038 using KPIM::BroadcastStatus; 00039 00040 #ifndef MAX_LINE 00041 #define MAX_LINE 4096 00042 #endif 00043 #ifndef INIT_MSGS 00044 #define INIT_MSGS 8 00045 #endif 00046 00047 // Regular expression to find the line that seperates messages in a mail 00048 // folder: 00049 #define MSG_SEPERATOR_START "From " 00050 #define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1) 00051 #define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]" 00052 00053 00054 //----------------------------------------------------------------------------- 00055 KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name) 00056 : KMFolderIndex(folder, name) 00057 { 00058 mStream = 0; 00059 mFilesLocked = false; 00060 mReadOnly = false; 00061 mLockType = lock_none; 00062 } 00063 00064 00065 //----------------------------------------------------------------------------- 00066 KMFolderMbox::~KMFolderMbox() 00067 { 00068 if (mOpenCount>0) close(true); 00069 if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() ); 00070 } 00071 00072 //----------------------------------------------------------------------------- 00073 int KMFolderMbox::open() 00074 { 00075 int rc = 0; 00076 00077 mOpenCount++; 00078 kmkernel->jobScheduler()->notifyOpeningFolder( folder() ); 00079 00080 if (mOpenCount > 1) return 0; // already open 00081 00082 assert(!folder()->name().isEmpty()); 00083 00084 mFilesLocked = false; 00085 mStream = fopen(QFile::encodeName(location()), "r+"); // messages file 00086 if (!mStream) 00087 { 00088 KNotifyClient::event( 0, "warning", 00089 i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno))); 00090 kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl; 00091 mOpenCount = 0; 00092 return errno; 00093 } 00094 00095 lock(); 00096 00097 if (!folder()->path().isEmpty()) 00098 { 00099 KMFolderIndex::IndexStatus index_status = indexStatus(); 00100 // test if index file exists and is up-to-date 00101 if (KMFolderIndex::IndexOk != index_status) 00102 { 00103 // only show a warning if the index file exists, otherwise it can be 00104 // silently regenerated 00105 if (KMFolderIndex::IndexTooOld == index_status) { 00106 QString msg = i18n("<qt><p>The index of folder '%2' seems " 00107 "to be out of date. To prevent message " 00108 "corruption the index will be " 00109 "regenerated. As a result deleted " 00110 "messages might reappear and status " 00111 "flags might be lost.</p>" 00112 "<p>Please read the corresponding entry " 00113 "in the <a href=\"%1\">FAQ section of the manual " 00114 "of KMail</a> for " 00115 "information about how to prevent this " 00116 "problem from happening again.</p></qt>") 00117 .arg("help:/kmail/faq.html#faq-index-regeneration") 00118 .arg(name()); 00119 // When KMail is starting up we have to show a non-blocking message 00120 // box so that the initialization can continue. We don't show a 00121 // queued message box when KMail isn't starting up because queued 00122 // message boxes don't have a "Don't ask again" checkbox. 00123 if (kmkernel->startingUp()) 00124 { 00125 KConfigGroup configGroup( KMKernel::config(), "Notification Messages" ); 00126 bool showMessage = 00127 configGroup.readBoolEntry( "showIndexRegenerationMessage", true ); 00128 if (showMessage) 00129 KMessageBox::queuedMessageBox( 0, KMessageBox::Information, 00130 msg, i18n("Index Out of Date"), 00131 KMessageBox::AllowLink ); 00132 } 00133 else 00134 { 00135 KCursorSaver idle(KBusyPtr::idle()); 00136 KMessageBox::information( 0, msg, i18n("Index Out of Date"), 00137 "showIndexRegenerationMessage", 00138 KMessageBox::AllowLink ); 00139 } 00140 } 00141 QString str; 00142 mIndexStream = 0; 00143 str = i18n("Folder `%1' changed. Recreating index.") 00144 .arg(name()); 00145 emit statusMsg(str); 00146 } else { 00147 mIndexStream = fopen(QFile::encodeName(indexLocation()), "r+"); // index file 00148 if ( mIndexStream ) { 00149 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); 00150 updateIndexStreamPtr(); 00151 } 00152 } 00153 00154 if (!mIndexStream) 00155 rc = createIndexFromContents(); 00156 else 00157 if (!readIndex()) 00158 rc = createIndexFromContents(); 00159 } 00160 else 00161 { 00162 mAutoCreateIndex = false; 00163 rc = createIndexFromContents(); 00164 } 00165 00166 mChanged = false; 00167 00168 fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); 00169 if (mIndexStream) 00170 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); 00171 00172 return rc; 00173 } 00174 00175 //---------------------------------------------------------------------------- 00176 int KMFolderMbox::canAccess() 00177 { 00178 assert(!folder()->name().isEmpty()); 00179 00180 if (access(QFile::encodeName(location()), R_OK | W_OK) != 0) { 00181 kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl; 00182 return 1; 00183 } 00184 return 0; 00185 } 00186 00187 //----------------------------------------------------------------------------- 00188 int KMFolderMbox::create(bool imap) 00189 { 00190 int rc; 00191 int old_umask; 00192 00193 Q_UNUSED(imap); 00194 00195 assert(!folder()->name().isEmpty()); 00196 assert(mOpenCount == 0); 00197 00198 kdDebug(5006) << "Creating folder " << name() << endl; 00199 if (access(QFile::encodeName(location()), F_OK) == 0) { 00200 kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl; 00201 kdDebug(5006) << "File:: " << endl; 00202 kdDebug(5006) << "Error " << endl; 00203 return EEXIST; 00204 } 00205 00206 old_umask = umask(077); 00207 mStream = fopen(QFile::encodeName(location()), "w+"); //sven; open RW 00208 umask(old_umask); 00209 00210 if (!mStream) return errno; 00211 00212 fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); 00213 00214 if (!folder()->path().isEmpty()) 00215 { 00216 old_umask = umask(077); 00217 mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW 00218 updateIndexStreamPtr(true); 00219 umask(old_umask); 00220 00221 if (!mIndexStream) return errno; 00222 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); 00223 } 00224 else 00225 { 00226 mAutoCreateIndex = false; 00227 } 00228 00229 mOpenCount++; 00230 mChanged = false; 00231 00232 rc = writeIndex(); 00233 if (!rc) lock(); 00234 return rc; 00235 } 00236 00237 00238 //----------------------------------------------------------------------------- 00239 void KMFolderMbox::close(bool aForced) 00240 { 00241 if (mOpenCount <= 0 || !mStream) return; 00242 if (mOpenCount > 0) mOpenCount--; 00243 if (mOpenCount > 0 && !aForced) return; 00244 #if 0 // removed hack that prevented closing system folders (see kmail-devel discussion about mail expiring) 00245 if ( (folder() != kmkernel->inboxFolder()) 00246 && folder()->isSystemFolder() && !aForced ) 00247 { 00248 mOpenCount = 1; 00249 return; 00250 } 00251 #endif 00252 00253 if (mAutoCreateIndex) 00254 { 00255 if (KMFolderIndex::IndexOk != indexStatus()) { 00256 kdDebug(5006) << "Critical error: " << location() << 00257 " has been modified by an external application while KMail was running." << endl; 00258 // exit(1); backed out due to broken nfs 00259 } 00260 00261 updateIndex(); 00262 writeConfig(); 00263 } 00264 00265 if (!noContent()) { 00266 if (mStream) unlock(); 00267 mMsgList.clear(true); 00268 00269 if (mStream) fclose(mStream); 00270 if (mIndexStream) { 00271 fclose(mIndexStream); 00272 updateIndexStreamPtr(true); 00273 } 00274 } 00275 mOpenCount = 0; 00276 mStream = 0; 00277 mIndexStream = 0; 00278 mFilesLocked = false; 00279 mUnreadMsgs = -1; 00280 00281 mMsgList.reset(INIT_MSGS); 00282 } 00283 00284 //----------------------------------------------------------------------------- 00285 void KMFolderMbox::sync() 00286 { 00287 if (mOpenCount > 0) 00288 if (!mStream || fsync(fileno(mStream)) || 00289 !mIndexStream || fsync(fileno(mIndexStream))) { 00290 kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").arg( indexLocation() ).arg(errno ? QString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug."))); 00291 } 00292 } 00293 00294 //----------------------------------------------------------------------------- 00295 int KMFolderMbox::lock() 00296 { 00297 int rc; 00298 struct flock fl; 00299 fl.l_type=F_WRLCK; 00300 fl.l_whence=0; 00301 fl.l_start=0; 00302 fl.l_len=0; 00303 fl.l_pid=-1; 00304 QCString cmd_str; 00305 assert(mStream != 0); 00306 mFilesLocked = false; 00307 mReadOnly = false; 00308 00309 switch( mLockType ) 00310 { 00311 case FCNTL: 00312 rc = fcntl(fileno(mStream), F_SETLKW, &fl); 00313 00314 if (rc < 0) 00315 { 00316 kdDebug(5006) << "Cannot lock folder `" << location() << "': " 00317 << strerror(errno) << " (" << errno << ")" << endl; 00318 mReadOnly = true; 00319 return errno; 00320 } 00321 00322 if (mIndexStream) 00323 { 00324 rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); 00325 00326 if (rc < 0) 00327 { 00328 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " 00329 << strerror(errno) << " (" << errno << ")" << endl; 00330 rc = errno; 00331 fl.l_type = F_UNLCK; 00332 /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl); 00333 mReadOnly = true; 00334 return rc; 00335 } 00336 } 00337 break; 00338 00339 case procmail_lockfile: 00340 cmd_str = "lockfile -l20 -r5 "; 00341 if (!mProcmailLockFileName.isEmpty()) 00342 cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); 00343 else 00344 cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); 00345 00346 rc = system( cmd_str.data() ); 00347 if( rc != 0 ) 00348 { 00349 kdDebug(5006) << "Cannot lock folder `" << location() << "': " 00350 << strerror(rc) << " (" << rc << ")" << endl; 00351 mReadOnly = true; 00352 return rc; 00353 } 00354 if( mIndexStream ) 00355 { 00356 cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); 00357 rc = system( cmd_str.data() ); 00358 if( rc != 0 ) 00359 { 00360 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " 00361 << strerror(rc) << " (" << rc << ")" << endl; 00362 mReadOnly = true; 00363 return rc; 00364 } 00365 } 00366 break; 00367 00368 case mutt_dotlock: 00369 cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(location())); 00370 rc = system( cmd_str.data() ); 00371 if( rc != 0 ) 00372 { 00373 kdDebug(5006) << "Cannot lock folder `" << location() << "': " 00374 << strerror(rc) << " (" << rc << ")" << endl; 00375 mReadOnly = true; 00376 return rc; 00377 } 00378 if( mIndexStream ) 00379 { 00380 cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(indexLocation())); 00381 rc = system( cmd_str.data() ); 00382 if( rc != 0 ) 00383 { 00384 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " 00385 << strerror(rc) << " (" << rc << ")" << endl; 00386 mReadOnly = true; 00387 return rc; 00388 } 00389 } 00390 break; 00391 00392 case mutt_dotlock_privileged: 00393 cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(location())); 00394 rc = system( cmd_str.data() ); 00395 if( rc != 0 ) 00396 { 00397 kdDebug(5006) << "Cannot lock folder `" << location() << "': " 00398 << strerror(rc) << " (" << rc << ")" << endl; 00399 mReadOnly = true; 00400 return rc; 00401 } 00402 if( mIndexStream ) 00403 { 00404 cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(indexLocation())); 00405 rc = system( cmd_str.data() ); 00406 if( rc != 0 ) 00407 { 00408 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " 00409 << strerror(rc) << " (" << rc << ")" << endl; 00410 mReadOnly = true; 00411 return rc; 00412 } 00413 } 00414 break; 00415 00416 case lock_none: 00417 default: 00418 break; 00419 } 00420 00421 00422 mFilesLocked = true; 00423 return 0; 00424 } 00425 00426 //------------------------------------------------------------- 00427 FolderJob* 00428 KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt, 00429 KMFolder *folder, QString, const AttachmentStrategy* ) const 00430 { 00431 MboxJob *job = new MboxJob( msg, jt, folder ); 00432 job->setParent( this ); 00433 return job; 00434 } 00435 00436 //------------------------------------------------------------- 00437 FolderJob* 00438 KMFolderMbox::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets, 00439 FolderJob::JobType jt, KMFolder *folder ) const 00440 { 00441 MboxJob *job = new MboxJob( msgList, sets, jt, folder ); 00442 job->setParent( this ); 00443 return job; 00444 } 00445 00446 //----------------------------------------------------------------------------- 00447 int KMFolderMbox::unlock() 00448 { 00449 int rc; 00450 struct flock fl; 00451 fl.l_type=F_UNLCK; 00452 fl.l_whence=0; 00453 fl.l_start=0; 00454 fl.l_len=0; 00455 QCString cmd_str; 00456 00457 assert(mStream != 0); 00458 mFilesLocked = false; 00459 00460 switch( mLockType ) 00461 { 00462 case FCNTL: 00463 if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl); 00464 fcntl(fileno(mStream), F_SETLK, &fl); 00465 rc = errno; 00466 break; 00467 00468 case procmail_lockfile: 00469 cmd_str = "rm -f "; 00470 if (!mProcmailLockFileName.isEmpty()) 00471 cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); 00472 else 00473 cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); 00474 00475 rc = system( cmd_str.data() ); 00476 if( mIndexStream ) 00477 { 00478 cmd_str = "rm -f " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); 00479 rc = system( cmd_str.data() ); 00480 } 00481 break; 00482 00483 case mutt_dotlock: 00484 cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(location())); 00485 rc = system( cmd_str.data() ); 00486 if( mIndexStream ) 00487 { 00488 cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(indexLocation())); 00489 rc = system( cmd_str.data() ); 00490 } 00491 break; 00492 00493 case mutt_dotlock_privileged: 00494 cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(location())); 00495 rc = system( cmd_str.data() ); 00496 if( mIndexStream ) 00497 { 00498 cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(indexLocation())); 00499 rc = system( cmd_str.data() ); 00500 } 00501 break; 00502 00503 case lock_none: 00504 default: 00505 rc = 0; 00506 break; 00507 } 00508 00509 return rc; 00510 } 00511 00512 00513 //----------------------------------------------------------------------------- 00514 KMFolderIndex::IndexStatus KMFolderMbox::indexStatus() 00515 { 00516 QFileInfo contInfo(location()); 00517 QFileInfo indInfo(indexLocation()); 00518 00519 if (!contInfo.exists()) return KMFolderIndex::IndexOk; 00520 if (!indInfo.exists()) return KMFolderIndex::IndexMissing; 00521 00522 // Check whether the mbox file is more than 5 seconds newer than the index 00523 // file. The 5 seconds are added to reduce the number of false alerts due 00524 // to slightly out of sync clocks of the NFS server and the local machine. 00525 return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) ) 00526 ? KMFolderIndex::IndexTooOld 00527 : KMFolderIndex::IndexOk; 00528 } 00529 00530 00531 //----------------------------------------------------------------------------- 00532 int KMFolderMbox::createIndexFromContents() 00533 { 00534 char line[MAX_LINE]; 00535 char status[8], xstatus[8]; 00536 QCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0; 00537 QCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr; 00538 QCString sizeServerStr, uidStr; 00539 bool atEof = false; 00540 bool inHeader = true; 00541 KMMsgInfo* mi; 00542 QString msgStr; 00543 QRegExp regexp(MSG_SEPERATOR_REGEX); 00544 int i, num, numStatus; 00545 short needStatus; 00546 00547 assert(mStream != 0); 00548 rewind(mStream); 00549 00550 mMsgList.clear(); 00551 00552 num = -1; 00553 numStatus= 11; 00554 off_t offs = 0; 00555 size_t size = 0; 00556 dateStr = ""; 00557 fromStr = ""; 00558 toStr = ""; 00559 subjStr = ""; 00560 *status = '\0'; 00561 *xstatus = '\0'; 00562 xmarkStr = ""; 00563 replyToIdStr = ""; 00564 replyToAuxIdStr = ""; 00565 referencesStr = ""; 00566 msgIdStr = ""; 00567 needStatus = 3; 00568 size_t sizeServer = 0; 00569 ulong uid = 0; 00570 00571 00572 while (!atEof) 00573 { 00574 off_t pos = ftell(mStream); 00575 if (!fgets(line, MAX_LINE, mStream)) atEof = true; 00576 00577 if (atEof || 00578 (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 && 00579 regexp.search(line) >= 0)) 00580 { 00581 size = pos - offs; 00582 pos = ftell(mStream); 00583 00584 if (num >= 0) 00585 { 00586 if (numStatus <= 0) 00587 { 00588 msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num); 00589 emit statusMsg(msgStr); 00590 numStatus = 10; 00591 } 00592 00593 if (size > 0) 00594 { 00595 msgIdStr = msgIdStr.stripWhiteSpace(); 00596 if( !msgIdStr.isEmpty() ) { 00597 int rightAngle; 00598 rightAngle = msgIdStr.find( '>' ); 00599 if( rightAngle != -1 ) 00600 msgIdStr.truncate( rightAngle + 1 ); 00601 } 00602 00603 replyToIdStr = replyToIdStr.stripWhiteSpace(); 00604 if( !replyToIdStr.isEmpty() ) { 00605 int rightAngle; 00606 rightAngle = replyToIdStr.find( '>' ); 00607 if( rightAngle != -1 ) 00608 replyToIdStr.truncate( rightAngle + 1 ); 00609 } 00610 00611 referencesStr = referencesStr.stripWhiteSpace(); 00612 if( !referencesStr.isEmpty() ) { 00613 int leftAngle, rightAngle; 00614 leftAngle = referencesStr.findRev( '<' ); 00615 if( ( leftAngle != -1 ) 00616 && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) { 00617 // use the last reference, instead of missing In-Reply-To 00618 replyToIdStr = referencesStr.mid( leftAngle ); 00619 } 00620 00621 // find second last reference 00622 leftAngle = referencesStr.findRev( '<', leftAngle - 1 ); 00623 if( leftAngle != -1 ) 00624 referencesStr = referencesStr.mid( leftAngle ); 00625 rightAngle = referencesStr.findRev( '>' ); 00626 if( rightAngle != -1 ) 00627 referencesStr.truncate( rightAngle + 1 ); 00628 00629 // Store the second to last reference in the replyToAuxIdStr 00630 // It is a good candidate for threading the message below if the 00631 // message In-Reply-To points to is not kept in this folder, 00632 // but e.g. in an Outbox 00633 replyToAuxIdStr = referencesStr; 00634 rightAngle = referencesStr.find( '>' ); 00635 if( rightAngle != -1 ) 00636 replyToAuxIdStr.truncate( rightAngle + 1 ); 00637 } 00638 00639 mi = new KMMsgInfo(folder()); 00640 mi->init( subjStr.stripWhiteSpace(), 00641 fromStr.stripWhiteSpace(), 00642 toStr.stripWhiteSpace(), 00643 0, KMMsgStatusNew, 00644 xmarkStr.stripWhiteSpace(), 00645 replyToIdStr, replyToAuxIdStr, msgIdStr, 00646 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, 00647 KMMsgMDNStateUnknown, offs, size, sizeServer, uid ); 00648 mi->setStatus(status, xstatus); 00649 mi->setDate( dateStr.stripWhiteSpace() ); 00650 mi->setDirty(false); 00651 mMsgList.append(mi); 00652 00653 *status = '\0'; 00654 *xstatus = '\0'; 00655 needStatus = 3; 00656 xmarkStr = ""; 00657 replyToIdStr = ""; 00658 replyToAuxIdStr = ""; 00659 referencesStr = ""; 00660 msgIdStr = ""; 00661 dateStr = ""; 00662 fromStr = ""; 00663 subjStr = ""; 00664 sizeServer = 0; 00665 uid = 0; 00666 } 00667 else num--,numStatus++; 00668 } 00669 00670 offs = ftell(mStream); 00671 num++; 00672 numStatus--; 00673 inHeader = true; 00674 continue; 00675 } 00676 // Is this a long header line? 00677 if (inHeader && (line[0]=='\t' || line[0]==' ')) 00678 { 00679 i = 0; 00680 while (line [i]=='\t' || line [i]==' ') i++; 00681 if (line [i] < ' ' && line [i]>0) inHeader = false; 00682 else if (lastStr) *lastStr += line + i; 00683 } 00684 else lastStr = 0; 00685 00686 if (inHeader && (line [0]=='\n' || line [0]=='\r')) 00687 inHeader = false; 00688 if (!inHeader) continue; 00689 00690 /* -sanders Make all messages read when auto-recreating index */ 00691 /* Reverted, as it breaks reading the sent mail status, for example. 00692 -till */ 00693 if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0) 00694 { 00695 for(i=0; i<4 && line[i+8] > ' '; i++) 00696 status[i] = line[i+8]; 00697 status[i] = '\0'; 00698 needStatus &= ~1; 00699 } 00700 else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0) 00701 { 00702 for(i=0; i<4 && line[i+10] > ' '; i++) 00703 xstatus[i] = line[i+10]; 00704 xstatus[i] = '\0'; 00705 needStatus &= ~2; 00706 } 00707 else if (strncasecmp(line,"X-KMail-Mark:",13)==0) 00708 xmarkStr = QCString(line+13); 00709 else if (strncasecmp(line,"In-Reply-To:",12)==0) { 00710 replyToIdStr = QCString(line+12); 00711 lastStr = &replyToIdStr; 00712 } 00713 else if (strncasecmp(line,"References:",11)==0) { 00714 referencesStr = QCString(line+11); 00715 lastStr = &referencesStr; 00716 } 00717 else if (strncasecmp(line,"Message-Id:",11)==0) { 00718 msgIdStr = QCString(line+11); 00719 lastStr = &msgIdStr; 00720 } 00721 else if (strncasecmp(line,"Date:",5)==0) 00722 { 00723 dateStr = QCString(line+5); 00724 lastStr = &dateStr; 00725 } 00726 else if (strncasecmp(line,"From:", 5)==0) 00727 { 00728 fromStr = QCString(line+5); 00729 lastStr = &fromStr; 00730 } 00731 else if (strncasecmp(line,"To:", 3)==0) 00732 { 00733 toStr = QCString(line+3); 00734 lastStr = &toStr; 00735 } 00736 else if (strncasecmp(line,"Subject:",8)==0) 00737 { 00738 subjStr = QCString(line+8); 00739 lastStr = &subjStr; 00740 } 00741 else if (strncasecmp(line,"X-Length:",9)==0) 00742 { 00743 sizeServerStr = QCString(line+9); 00744 sizeServer = sizeServerStr.toULong(); 00745 lastStr = &sizeServerStr; 00746 } 00747 else if (strncasecmp(line,"X-UID:",6)==0) 00748 { 00749 uidStr = QCString(line+6); 00750 uid = uidStr.toULong(); 00751 lastStr = &uidStr; 00752 } 00753 } 00754 00755 if (mAutoCreateIndex) 00756 { 00757 emit statusMsg(i18n("Writing index file")); 00758 writeIndex(); 00759 } 00760 else mHeaderOffset = 0; 00761 00762 correctUnreadMsgsCount(); 00763 00764 if (kmkernel->outboxFolder() == folder() && count() > 0) 00765 KMessageBox::queuedMessageBox(0, KMessageBox::Information, 00766 i18n("Your outbox contains messages which were " 00767 "most-likely not created by KMail;\nplease remove them from there if you " 00768 "do not want KMail to send them.")); 00769 00770 if ( folder()->parent() ) 00771 folder()->parent()->manager()->invalidateFolder( kmkernel->msgDict(), folder() ); 00772 return 0; 00773 } 00774 00775 00776 //----------------------------------------------------------------------------- 00777 KMMessage* KMFolderMbox::readMsg(int idx) 00778 { 00779 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; 00780 00781 assert(mi!=0 && !mi->isMessage()); 00782 assert(mStream != 0); 00783 00784 KMMessage* msg = new KMMessage(*mi); 00785 msg->fromDwString( getDwString( idx ) ); 00786 mMsgList.set(idx,&msg->toMsgBase()); 00787 00788 return msg; 00789 } 00790 00791 00792 #define STRDIM(x) (sizeof(x)/sizeof(*x)-1) 00793 // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion 00794 static size_t unescapeFrom( char* str, size_t strLen ) { 00795 if ( !str ) 00796 return 0; 00797 if ( strLen <= STRDIM(">From ") ) 00798 return strLen; 00799 00800 // yes, *d++ = *s++ is a no-op as long as d == s (until after the 00801 // first >From_), but writes are cheap compared to reads and the 00802 // data is already in the cache from the read, so special-casing 00803 // might even be slower... 00804 const char * s = str; 00805 char * d = str; 00806 const char * const e = str + strLen - STRDIM(">From "); 00807 00808 while ( s < e ) { 00809 if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end! 00810 *d++ = *s++; // == '\n' 00811 *d++ = *s++; // == '>' 00812 while ( s < e && *s == '>' ) 00813 *d++ = *s++; 00814 if ( qstrncmp( s, "From ", STRDIM("From ") ) == 0 ) 00815 --d; 00816 } 00817 *d++ = *s++; // yes, s might be e here, but e is not the end :-) 00818 } 00819 // copy the rest: 00820 while ( s < str + strLen ) 00821 *d++ = *s++; 00822 if ( d < s ) // only NUL-terminate if it's shorter 00823 *d = 0; 00824 00825 return d - str; 00826 } 00827 00828 //static 00829 QCString KMFolderMbox::escapeFrom( const QCString & str ) { 00830 const unsigned int strLen = str.length(); 00831 if ( strLen <= STRDIM("From ") ) 00832 return str; 00833 // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6 00834 QCString result( int( strLen + 5 ) / 6 * 7 + 1 ); 00835 00836 const char * s = str.data(); 00837 const char * const e = s + strLen - STRDIM("From "); 00838 char * d = result.data(); 00839 00840 bool onlyAnglesAfterLF = false; // dont' match ^From_ 00841 while ( s < e ) { 00842 switch ( *s ) { 00843 case '\n': 00844 onlyAnglesAfterLF = true; 00845 break; 00846 case '>': 00847 break; 00848 case 'F': 00849 if ( onlyAnglesAfterLF && qstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 ) 00850 *d++ = '>'; 00851 // fall through 00852 default: 00853 onlyAnglesAfterLF = false; 00854 break; 00855 } 00856 *d++ = *s++; 00857 } 00858 while ( s < str.data() + strLen ) 00859 *d++ = *s++; 00860 00861 result.truncate( d - result.data() ); 00862 return result; 00863 } 00864 00865 #undef STRDIM 00866 00867 //----------------------------------------------------------------------------- 00868 QCString& KMFolderMbox::getMsgString(int idx, QCString &mDest) 00869 { 00870 unsigned long msgSize; 00871 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; 00872 00873 assert(mi!=0); 00874 assert(mStream != 0); 00875 00876 msgSize = mi->msgSize(); 00877 mDest.resize(msgSize+2); 00878 00879 fseek(mStream, mi->folderOffset(), SEEK_SET); 00880 fread(mDest.data(), msgSize, 1, mStream); 00881 mDest[msgSize] = '\0'; 00882 00883 size_t newMsgSize = unescapeFrom( mDest.data(), msgSize ); 00884 newMsgSize = crlf2lf( mDest.data(), newMsgSize ); 00885 00886 return mDest; 00887 } 00888 00889 00890 //----------------------------------------------------------------------------- 00891 DwString KMFolderMbox::getDwString(int idx) 00892 { 00893 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; 00894 00895 assert(mi!=0); 00896 assert(mStream != 0); 00897 00898 size_t msgSize = mi->msgSize(); 00899 char* msgText = new char[ msgSize + 1 ]; 00900 00901 fseek(mStream, mi->folderOffset(), SEEK_SET); 00902 fread(msgText, msgSize, 1, mStream); 00903 msgText[msgSize] = '\0'; 00904 00905 size_t newMsgSize = unescapeFrom( msgText, msgSize ); 00906 newMsgSize = crlf2lf( msgText, newMsgSize ); 00907 00908 DwString msgStr; 00909 // the DwString takes possession of msgText, so we must not delete msgText 00910 msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize ); 00911 return msgStr; 00912 } 00913 00914 00915 //----------------------------------------------------------------------------- 00916 int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret ) 00917 { 00918 if (!canAddMsgNow(aMsg, aIndex_ret)) return 0; 00919 bool opened = false; 00920 QCString msgText; 00921 char endStr[3]; 00922 int idx = -1, rc; 00923 KMFolder* msgParent; 00924 bool editing = false; 00925 int growth = 0; 00926 00927 /* Then we can also disable it completely, this wastes time, at least for IMAP 00928 if (KMFolder::IndexOk != indexStatus()) { 00929 kdDebug(5006) << "Critical error: " << location() << 00930 " has been modified by an external application while KMail was running." << endl; 00931 // exit(1); backed out due to broken nfs 00932 } */ 00933 00934 if (!mStream) 00935 { 00936 opened = true; 00937 rc = open(); 00938 kdDebug(5006) << "KMFolderMBox::addMsg-open: " << rc << " of folder: " << label() << endl; 00939 if (rc) return rc; 00940 } 00941 00942 // take message out of the folder it is currently in, if any 00943 msgParent = aMsg->parent(); 00944 if (msgParent) 00945 { 00946 if ( msgParent== folder() ) 00947 { 00948 if (kmkernel->folderIsDraftOrOutbox( folder() )) 00949 //special case for Edit message. 00950 { 00951 kdDebug(5006) << "Editing message in outbox or drafts" << endl; 00952 editing = true; 00953 } 00954 else 00955 return 0; 00956 } 00957 00958 idx = msgParent->find(aMsg); 00959 msgParent->getMsg( idx ); 00960 } 00961 00962 if (folderType() != KMFolderTypeImap) 00963 { 00964 /* 00965 QFile fileD0( "testdat_xx-kmfoldermbox-0" ); 00966 if( fileD0.open( IO_WriteOnly ) ) { 00967 QDataStream ds( &fileD0 ); 00968 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); 00969 fileD0.close(); // If data is 0 we just create a zero length file. 00970 } 00971 */ 00972 aMsg->setStatusFields(); 00973 /* 00974 QFile fileD1( "testdat_xx-kmfoldermbox-1" ); 00975 if( fileD1.open( IO_WriteOnly ) ) { 00976 QDataStream ds( &fileD1 ); 00977 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); 00978 fileD1.close(); // If data is 0 we just create a zero length file. 00979 } 00980 */ 00981 if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by 00982 aMsg->removeHeaderField("Content-Type"); // the line above 00983 } 00984 msgText = escapeFrom( aMsg->asString() ); 00985 size_t len = msgText.length(); 00986 00987 assert(mStream != 0); 00988 clearerr(mStream); 00989 if (len <= 0) 00990 { 00991 kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; 00992 if (opened) close(); 00993 return 0; 00994 } 00995 00996 // Make sure the file is large enough to check for an end 00997 // character 00998 fseek(mStream, 0, SEEK_END); 00999 off_t revert = ftell(mStream); 01000 if (ftell(mStream) >= 2) { 01001 // write message to folder file 01002 fseek(mStream, -2, SEEK_END); 01003 fread(endStr, 1, 2, mStream); // ensure separating empty line 01004 if (ftell(mStream) > 0 && endStr[0]!='\n') { 01005 ++growth; 01006 if (endStr[1]!='\n') { 01007 //printf ("****endStr[1]=%c\n", endStr[1]); 01008 fwrite("\n\n", 1, 2, mStream); 01009 ++growth; 01010 } 01011 else fwrite("\n", 1, 1, mStream); 01012 } 01013 } 01014 fseek(mStream,0,SEEK_END); // this is needed on solaris and others 01015 int error = ferror(mStream); 01016 if (error) 01017 { 01018 if (opened) close(); 01019 return error; 01020 } 01021 01022 QCString messageSeparator( aMsg->mboxMessageSeparator() ); 01023 fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream ); 01024 off_t offs = ftell(mStream); 01025 fwrite(msgText, len, 1, mStream); 01026 if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream); 01027 fflush(mStream); 01028 size_t size = ftell(mStream) - offs; 01029 01030 error = ferror(mStream); 01031 if (error) { 01032 kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl; 01033 if (ftell(mStream) > revert) { 01034 kdDebug(5006) << "Undoing changes" << endl; 01035 truncate( QFile::encodeName(location()), revert ); 01036 } 01037 kmkernel->emergencyExit( i18n("Could not add message to folder: ") + QString::fromLocal8Bit(strerror(errno))); 01038 01039 /* This code is not 100% reliable 01040 bool busy = kmkernel->kbp()->isBusy(); 01041 if (busy) kmkernel->kbp()->idle(); 01042 KMessageBox::sorry(0, 01043 i18n("Unable to add message to folder.\n" 01044 "(No space left on device or insufficient quota?)\n" 01045 "Free space and sufficient quota are required to continue safely.")); 01046 if (busy) kmkernel->kbp()->busy(); 01047 if (opened) close(); 01048 kmkernel->kbp()->idle(); 01049 */ 01050 return error; 01051 } 01052 01053 if (msgParent) { 01054 if (idx >= 0) msgParent->take(idx); 01055 } 01056 // if (mAccount) aMsg->removeHeaderField("X-UID"); 01057 01058 if (aMsg->isUnread() || aMsg->isNew() || 01059 (folder() == kmkernel->outboxFolder())) { 01060 if (mUnreadMsgs == -1) mUnreadMsgs = 1; 01061 else ++mUnreadMsgs; 01062 if ( !mQuiet ) 01063 emit numUnreadMsgsChanged( folder() ); 01064 } 01065 ++mTotalMsgs; 01066 01067 // store information about the position in the folder file in the message 01068 aMsg->setParent(folder()); 01069 aMsg->setFolderOffset(offs); 01070 aMsg->setMsgSize(size); 01071 idx = mMsgList.append(&aMsg->toMsgBase()); 01072 if (aMsg->getMsgSerNum() <= 0) 01073 aMsg->setMsgSerNum(); 01074 01075 // change the length of the previous message to encompass white space added 01076 if ((idx > 0) && (growth > 0)) { 01077 // don't grow if a deleted message claims space at the end of the file 01078 if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() ) 01079 mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth ); 01080 } 01081 01082 // write index entry if desired 01083 if (mAutoCreateIndex) 01084 { 01085 assert(mIndexStream != 0); 01086 clearerr(mIndexStream); 01087 fseek(mIndexStream, 0, SEEK_END); 01088 revert = ftell(mIndexStream); 01089 01090 KMMsgBase * mb = &aMsg->toMsgBase(); 01091 int len; 01092 const uchar *buffer = mb->asIndexString(len); 01093 fwrite(&len,sizeof(len), 1, mIndexStream); 01094 mb->setIndexOffset( ftell(mIndexStream) ); 01095 mb->setIndexLength( len ); 01096 if(fwrite(buffer, len, 1, mIndexStream) != 1) 01097 kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; 01098 01099 fflush(mIndexStream); 01100 error = ferror(mIndexStream); 01101 01102 error |= appendtoMsgDict(idx); 01103 01104 if (error) { 01105 kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; 01106 if (ftell(mIndexStream) > revert) { 01107 kdWarning(5006) << "Undoing changes" << endl; 01108 truncate( QFile::encodeName(indexLocation()), revert ); 01109 } 01110 if ( errno ) 01111 kmkernel->emergencyExit( i18n("Could not add message to folder:") + QString::fromLocal8Bit(strerror(errno))); 01112 else 01113 kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") ); 01114 01115 /* This code may not be 100% reliable 01116 bool busy = kmkernel->kbp()->isBusy(); 01117 if (busy) kmkernel->kbp()->idle(); 01118 KMessageBox::sorry(0, 01119 i18n("Unable to add message to folder.\n" 01120 "(No space left on device or insufficient quota?)\n" 01121 "Free space and sufficient quota are required to continue safely.")); 01122 if (busy) kmkernel->kbp()->busy(); 01123 if (opened) close(); 01124 */ 01125 return error; 01126 } 01127 } 01128 01129 // some "paper work" 01130 if (aIndex_ret) *aIndex_ret = idx; 01131 emitMsgAddedSignals(idx); 01132 if (opened) close(); 01133 01134 // All streams have been flushed without errors if we arrive here 01135 // Return success! 01136 // (Don't return status of stream, it may have been closed already.) 01137 return 0; 01138 } 01139 01140 int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done ) 01141 { 01142 int rc = 0; 01143 QCString mtext; 01144 unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() : 01145 QMIN( mMsgList.count(), startIndex + nbMessages ); 01146 //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl; 01147 for(unsigned int idx = startIndex; idx < stopIndex; ++idx) { 01148 KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx); 01149 size_t msize = mi->msgSize(); 01150 if (mtext.size() < msize + 2) 01151 mtext.resize(msize+2); 01152 off_t folder_offset = mi->folderOffset(); 01153 01154 //now we need to find the separator! grr... 01155 for(off_t i = folder_offset-25; true; i -= 20) { 01156 off_t chunk_offset = i <= 0 ? 0 : i; 01157 if(fseek(mStream, chunk_offset, SEEK_SET) == -1) { 01158 rc = errno; 01159 break; 01160 } 01161 if (mtext.size() < 20) 01162 mtext.resize(20); 01163 fread(mtext.data(), 20, 1, mStream); 01164 if(i <= 0) { //woops we've reached the top of the file, last try.. 01165 if ( mtext.contains( "from ", false ) ) { 01166 if (mtext.size() < (size_t)folder_offset) 01167 mtext.resize(folder_offset); 01168 if(fseek(mStream, chunk_offset, SEEK_SET) == -1 || 01169 !fread(mtext.data(), folder_offset, 1, mStream) || 01170 !fwrite(mtext.data(), folder_offset, 1, tmpfile)) { 01171 rc = errno; 01172 break; 01173 } 01174 offs += folder_offset; 01175 } else { 01176 rc = 666; 01177 } 01178 break; 01179 } else { 01180 int last_crlf = -1; 01181 for(int i2 = 0; i2 < 20; i2++) { 01182 if(*(mtext.data()+i2) == '\n') 01183 last_crlf = i2; 01184 } 01185 if(last_crlf != -1) { 01186 int size = folder_offset - (i + last_crlf+1); 01187 if ((int)mtext.size() < size) 01188 mtext.resize(size); 01189 if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 || 01190 !fread(mtext.data(), size, 1, mStream) || 01191 !fwrite(mtext.data(), size, 1, tmpfile)) { 01192 rc = errno; 01193 break; 01194 } 01195 offs += size; 01196 break; 01197 } 01198 } 01199 } 01200 if (rc) 01201 break; 01202 01203 //now actually write the message 01204 if(fseek(mStream, folder_offset, SEEK_SET) == -1 || 01205 !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) { 01206 rc = errno; 01207 break; 01208 } 01209 mi->setFolderOffset(offs); 01210 offs += msize; 01211 } 01212 done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors 01213 return rc; 01214 } 01215 01216 //----------------------------------------------------------------------------- 01217 int KMFolderMbox::compact( bool silent ) 01218 { 01219 // This is called only when the user explicitely requests compaction, 01220 // so we don't check needsCompact. 01221 int openCount = mOpenCount; 01222 01223 KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ ); 01224 int rc = job->executeNow( silent ); 01225 // Note that job autodeletes itself. 01226 01227 if (openCount > 0) 01228 { 01229 open(); 01230 mOpenCount = openCount; 01231 } 01232 // If this is the current folder, the changed signal will ultimately call 01233 // KMHeaders::setFolderInfoStatus which will override the message, so save/restore it 01234 QString statusMsg = BroadcastStatus::instance()->statusMsg(); 01235 emit changed(); 01236 BroadcastStatus::instance()->setStatusMsg( statusMsg ); 01237 return rc; 01238 } 01239 01240 01241 //----------------------------------------------------------------------------- 01242 void KMFolderMbox::setLockType( LockType ltype ) 01243 { 01244 mLockType = ltype; 01245 } 01246 01247 //----------------------------------------------------------------------------- 01248 void KMFolderMbox::setProcmailLockFileName( const QString &fname ) 01249 { 01250 mProcmailLockFileName = fname; 01251 } 01252 01253 //----------------------------------------------------------------------------- 01254 int KMFolderMbox::removeContents() 01255 { 01256 int rc = 0; 01257 rc = unlink(QFile::encodeName(location())); 01258 return rc; 01259 } 01260 01261 //----------------------------------------------------------------------------- 01262 int KMFolderMbox::expungeContents() 01263 { 01264 int rc = 0; 01265 if (truncate(QFile::encodeName(location()), 0)) 01266 rc = errno; 01267 return rc; 01268 } 01269 01270 //----------------------------------------------------------------------------- 01271 #include "kmfoldermbox.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.3.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 21 19:46:48 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003