00001
00002
00003
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
00048
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;
00081
00082 assert(!folder()->name().isEmpty());
00083
00084 mFilesLocked = false;
00085 mStream = fopen(QFile::encodeName(location()), "r+");
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
00101 if (KMFolderIndex::IndexOk != index_status)
00102 {
00103
00104
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
00120
00121
00122
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+");
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+");
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+");
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
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 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
00523
00524
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
00618 replyToIdStr = referencesStr.mid( leftAngle );
00619 }
00620
00621
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
00630
00631
00632
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
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
00691
00692
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 mMsgList.set(idx,&msg->toMsgBase());
00786 msg->fromDwString( getDwString( idx ) );
00787
00788 return msg;
00789 }
00790
00791
00792 #define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
00793
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"