// kmfoldermbox.cpp // Author: Stefan Taferner #include #include #include #include #include "kmfoldermbox.h" #include "kmfoldermgr.h" #include "kmmessage.h" #include "kmundostack.h" #include "kbusyptr.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_FCNTL_H #include #endif #include #include #include #include #ifndef isblank # define isblank(x) ((x)==' '||(x)=='\t') #endif #ifndef MAX_LINE #define MAX_LINE 4096 #endif #ifndef INIT_MSGS #define INIT_MSGS 8 #endif // Regular expression to find the line that seperates messages in a mail // folder: #define MSG_SEPERATOR_START "From " #define MSG_SEPERATOR_REGEX "^From .*..:...*$" static short msgSepLen = strlen(MSG_SEPERATOR_START); //----------------------------------------------------------------------------- KMMboxJob::KMMboxJob( KMMessage *msg, JobType jt , KMFolder *folder ) : KMFolderJob( msg, jt, folder ) { } //----------------------------------------------------------------------------- KMMboxJob::KMMboxJob( QPtrList& msgList, const QString& sets, JobType jt, KMFolder *folder ) : KMFolderJob( msgList, sets, jt, folder ) { } //----------------------------------------------------------------------------- KMMboxJob::~KMMboxJob() { } //----------------------------------------------------------------------------- void KMMboxJob::execute() { QTimer::singleShot( 0, this, SLOT(startJob()) ); } //----------------------------------------------------------------------------- void KMMboxJob::expireMessages() { int days = 0; int maxUnreadTime = 0; int maxReadTime = 0; const KMMsgBase *mb = 0; QValueList rmvMsgList; int i = 0; time_t msgTime, maxTime = 0; days = mParent->daysToExpire( mParent->getUnreadExpireAge(), mParent->getUnreadExpireUnits() ); if (days > 0) { kdDebug(5006) << "deleting unread older than "<< days << " days" << endl; maxUnreadTime = time(0) - days * 3600 * 24; } days = mParent->daysToExpire( mParent->getReadExpireAge(), mParent->getReadExpireUnits() ); if (days > 0) { kdDebug(5006) << "deleting read older than "<< days << " days" << endl; maxReadTime = time(0) - days * 3600 * 24; } if ((maxUnreadTime == 0) && (maxReadTime == 0)) { return; } mParent->open(); for( i=mParent->count()-1; i>=0; i-- ) { mb = mParent->getMsgBase(i); if (mb == 0) { continue; } msgTime = mb->date(); if (mb->isUnread()) { maxTime = maxUnreadTime; } else { maxTime = maxReadTime; } if (msgTime < maxTime) { mParent->removeMsg( i ); } } mParent->close(); return; } //----------------------------------------------------------------------------- void KMMboxJob::setParent( KMFolderMbox *parent ) { mParent = parent; } //----------------------------------------------------------------------------- void KMMboxJob::startJob() { switch( mType ) { case tGetMessage: { KMMessage* msg = mParent->getMsg( mParent->find( mMsgList.first() ) ); emit messageRetrieved( msg ); } break; case tDeleteMessage: { mParent->removeMsg( mMsgList ); } break; case tPutMessage: { mParent->addMsg( mMsgList.first() ); emit messageStored( mMsgList.first() ); } break; case tExpireMessages: { expireMessages(); } break; case tCopyMessage: case tCreateFolder: case tGetFolder: case tListDirectory: kdDebug(5006)<removeJobFromList( this ); delete this; } //----------------------------------------------------------------------------- KMFolderMbox::KMFolderMbox(KMFolderDir* aParent, const QString& aName) : KMFolderMboxInherited(aParent, aName) { mStream = 0; mFilesLocked = FALSE; mLockType = None; } //----------------------------------------------------------------------------- KMFolderMbox::~KMFolderMbox() { if (mOpenCount>0) close(TRUE); if (kernel->undoStack()) kernel->undoStack()->folderDestroyed(this); } //----------------------------------------------------------------------------- int KMFolderMbox::open() { int rc = 0; mOpenCount++; if (mOpenCount > 1) return 0; // already open assert(name() != ""); mFilesLocked = FALSE; mStream = fopen(location().local8Bit(), "r+"); // messages file if (!mStream) { KNotifyClient::event("warning", i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno))); kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl; mOpenCount = 0; return errno; } lock(); if (!path().isEmpty()) { KMFolderIndex::IndexStatus index_status = indexStatus(); // test if index file exists and is up-to-date if (KMFolderIndex::IndexOk != index_status) { // only show a warning if the index file exists, otherwise it can be // silently regenerated if (KMFolderIndex::IndexTooOld == index_status) { QString msg = i18n("

The index of folder '%2' seems " "to be out of date. To prevent message " "corruption the index will be " "regenerated. As a result deleted " "messages might reappear and status " "flags might be lost.

" "

Please read the corresponding entry " "in the FAQ section of the manual " "of KMail for " "information about how to prevent this " "problem from happening again.

") .arg("help:/kmail/faq.html#faq-index-regeneration") .arg(name()); // When KMail is starting up we have to show a non-blocking message // box so that the initialization can continue. We don't show a // queued message box when KMail isn't starting up because queued // message boxes don't have a "Don't ask again" checkbox. if (kernel->startingUp()) { KConfigGroup configGroup( KMKernel::config(), "Notification Messages" ); bool showMessage = configGroup.readBoolEntry( "showIndexRegenerationMessage", true ); if (showMessage) KMessageBox::queuedMessageBox( 0, KMessageBox::Information, msg, i18n("Index Out of Date"), KMessageBox::AllowLink ); } else { bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::information( 0, msg, i18n("Index Out of Date"), "showIndexRegenerationMessage", KMessageBox::AllowLink ); if (busy) kernel->kbp()->busy(); } // ######### FIXME-AFTER-MSG-FREEZE: Delete this after the msg freeze if( 0 ) { KMessageBox::information( 0, i18n("

The index of folder '%1' seems " "to be out of date. To prevent message " "corruption the index will be " "regenerated. As a result deleted " "messages might reappear and status " "flags might be lost.

" "

Please read the corresponding entry " "in the FAQ section of the manual of " "KMail for " "information about how to prevent this " "problem from happening again.

") .arg(name()), i18n("Index Out of Date"), "dontshowIndexRegenerationWarning"); } // ######### end of FIXME-AFTER-MSG-FREEZE: Delete this after the msg freeze } QString str; mIndexStream = 0; str = i18n("Folder `%1' changed. Recreating index.") .arg(name()); emit statusMsg(str); } else { mIndexStream = fopen(indexLocation().local8Bit(), "r+"); // index file updateIndexStreamPtr(); } if (!mIndexStream) rc = createIndexFromContents(); else if (!readIndex()) rc = createIndexFromContents(); } else { mAutoCreateIndex = FALSE; rc = createIndexFromContents(); } mQuiet = 0; mChanged = FALSE; return rc; } //---------------------------------------------------------------------------- int KMFolderMbox::canAccess() { assert(name() != ""); if (access(location().local8Bit(), R_OK | W_OK) != 0) { kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl; return 1; } return 0; } //----------------------------------------------------------------------------- int KMFolderMbox::create(bool imap) { int rc; int old_umask; assert(name() != ""); assert(mOpenCount == 0); kdDebug(5006) << "Creating folder " << endl; if (access(location().local8Bit(), F_OK) == 0) { kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl; kdDebug(5006) << "File:: " << endl; kdDebug(5006) << "Error " << endl; return EEXIST; } old_umask = umask(077); mStream = fopen(location().local8Bit(), "w+"); //sven; open RW umask(old_umask); if (!mStream) return errno; if (!path().isEmpty()) { old_umask = umask(077); mIndexStream = fopen(indexLocation().local8Bit(), "w+"); //sven; open RW updateIndexStreamPtr(TRUE); umask(old_umask); if (!mIndexStream) return errno; } else { mAutoCreateIndex = FALSE; } mOpenCount++; mQuiet = 0; mChanged = FALSE; if (imap) { readConfig(); mUnreadMsgs = -1; } rc = writeIndex(); if (!rc) lock(); return rc; } //----------------------------------------------------------------------------- void KMFolderMbox::close(bool aForced) { if (mOpenCount <= 0 || !mStream) return; if (mOpenCount > 0) mOpenCount--; if (mOpenCount > 0 && !aForced) return; if ((this != kernel->inboxFolder()) && isSystemFolder() && !aForced) return; if (mAutoCreateIndex) { if (KMFolderIndex::IndexOk != indexStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } updateIndex(); writeConfig(); } unlock(); mMsgList.clear(TRUE); if (mStream) fclose(mStream); if (mIndexStream) { fclose(mIndexStream); updateIndexStreamPtr(TRUE); } mOpenCount = 0; mStream = 0; mIndexStream = 0; mFilesLocked = FALSE; mUnreadMsgs = -1; mMsgList.reset(INIT_MSGS); } //----------------------------------------------------------------------------- void KMFolderMbox::sync() { if (mOpenCount > 0) if (!mStream || fsync(fileno(mStream)) || !mIndexStream || fsync(fileno(mIndexStream))) { kernel->emergencyExit( i18n("Not enough free disk space." )); } } //----------------------------------------------------------------------------- int KMFolderMbox::lock() { int rc; struct flock fl; fl.l_type=F_WRLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; fl.l_pid=-1; QCString cmd_str; assert(mStream != 0); mFilesLocked = FALSE; switch( mLockType ) { case FCNTL: rc = fcntl(fileno(mStream), F_SETLKW, &fl); if (rc < 0) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(errno) << " (" << errno << ")" << endl; return errno; } if (mIndexStream) { rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); if (rc < 0) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(errno) << " (" << errno << ")" << endl; rc = errno; fl.l_type = F_UNLCK; rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); return rc; } } break; case procmail_lockfile: cmd_str = "lockfile -l20 -r5 "; if (!mProcmailLockFileName.isEmpty()) cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); else cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } if( mIndexStream ) { cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } } break; case mutt_dotlock: cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } if( mIndexStream ) { cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } } break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } if( mIndexStream ) { cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; return rc; } } break; case None: default: break; } mFilesLocked = TRUE; return 0; } //----------------------------------------------------------------------------- int KMFolderMbox::unlock() { int rc; struct flock fl; fl.l_type=F_UNLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; QCString cmd_str; assert(mStream != 0); mFilesLocked = FALSE; switch( mLockType ) { case FCNTL: if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl); fcntl(fileno(mStream), F_SETLK, F_UNLCK); rc = errno; break; case procmail_lockfile: cmd_str = "rm -f "; if (!mProcmailLockFileName.isEmpty()) cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName)); else cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock")); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "rm -f " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock")); rc = system( cmd_str.data() ); } break; case mutt_dotlock: cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); } break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); } break; case None: default: rc = 0; break; } return rc; } //----------------------------------------------------------------------------- KMFolderIndex::IndexStatus KMFolderMbox::indexStatus() { QFileInfo contInfo(location()); QFileInfo indInfo(indexLocation()); if (!contInfo.exists()) return KMFolderIndex::IndexOk; if (!indInfo.exists()) return KMFolderIndex::IndexMissing; // Check whether the mbox file is more than 5 seconds newer than the index // file. The 5 seconds are added to reduce the number of false alerts due // to slightly out of sync clocks of the NFS server and the local machine. return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) ) ? KMFolderIndex::IndexTooOld : KMFolderIndex::IndexOk; } //------------------------------------------------------------- KMFolderJob* KMFolderMbox::createJob( KMMessage *msg, KMFolderJob::JobType jt, KMFolder *folder ) { KMMboxJob *job = new KMMboxJob( msg, jt, folder ); job->setParent( this ); mJobList.append( job ); return job; } //------------------------------------------------------------- KMFolderJob* KMFolderMbox::createJob( QPtrList& msgList, const QString& sets, KMFolderJob::JobType jt, KMFolder *folder ) { KMMboxJob *job = new KMMboxJob( msgList, sets, jt, folder ); job->setParent( this ); mJobList.append( job ); return job; } //----------------------------------------------------------------------------- int KMFolderMbox::createIndexFromContents() { char line[MAX_LINE]; char status[8], xstatus[8]; QCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0; QCString replyToIdStr, referencesStr, msgIdStr; bool atEof = FALSE; bool inHeader = TRUE; KMMsgInfo* mi; QString msgStr; QRegExp regexp(MSG_SEPERATOR_REGEX); int i, num, numStatus; short needStatus; quiet(TRUE); assert(mStream != 0); rewind(mStream); mMsgList.clear(); num = -1; numStatus= 11; off_t offs = 0; size_t size = 0; dateStr = ""; fromStr = ""; toStr = ""; subjStr = ""; *status = '\0'; *xstatus = '\0'; xmarkStr = ""; replyToIdStr = ""; msgIdStr = ""; needStatus = 3; while (!atEof) { off_t pos = ftell(mStream); if (!fgets(line, MAX_LINE, mStream)) atEof = TRUE; if (atEof || (strncmp(line,MSG_SEPERATOR_START, msgSepLen)==0 && regexp.search(line) >= 0)) { size = pos - offs; pos = ftell(mStream); if (num >= 0) { if (numStatus <= 0) { msgStr = i18n("Creating index file: %n message done", "Creating index file: %n messages done", num); emit statusMsg(msgStr); numStatus = 10; } if (size > 0) { if ((replyToIdStr.isEmpty() || (replyToIdStr[0] != '<')) && !referencesStr.isEmpty() && referencesStr[0] == '<') { replyToIdStr = referencesStr; } mi = new KMMsgInfo(this); mi->init(subjStr, fromStr, toStr, 0, KMMsgStatusNew, xmarkStr, replyToIdStr, msgIdStr, KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, KMMsgMDNStateUnknown, offs, size); mi->setStatus("RO","O"); mi->setDate(dateStr); mi->setDirty(FALSE); mMsgList.append(mi); *status = '\0'; *xstatus = '\0'; needStatus = 3; xmarkStr = ""; replyToIdStr = ""; referencesStr = ""; msgIdStr = ""; dateStr = ""; fromStr = ""; subjStr = ""; } else num--,numStatus++; } offs = ftell(mStream); num++; numStatus--; inHeader = TRUE; continue; } // Is this a long header line? if (inHeader && (line[0]=='\t' || line[0]==' ')) { i = 0; while (line [i]=='\t' || line [i]==' ') i++; if (line [i] < ' ' && line [i]>0) inHeader = FALSE; else if (lastStr) *lastStr += line + i; } else lastStr = 0; if (inHeader && (line [0]=='\n' || line [0]=='\r')) inHeader = FALSE; if (!inHeader) continue; /* -sanders Make all messages read when auto-recreating index if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0 && isblank(line[7])) { for(i=0; i<4 && line[i+8] > ' '; i++) status[i] = line[i+8]; status[i] = '\0'; needStatus &= ~1; } else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0 && isblank(line[9])) { for(i=0; i<4 && line[i+10] > ' '; i++) xstatus[i] = line[i+10]; xstatus[i] = '\0'; needStatus &= ~2; } else*/ if (strncasecmp(line,"X-KMail-Mark:",13)==0 && isblank(line[13])) xmarkStr = QCString(line+14); else if (strncasecmp(line,"In-Reply-To:",12)==0 && isblank(line[12])) { int rightAngle; replyToIdStr = QCString(line+13); rightAngle = replyToIdStr.find( '>' ); if (rightAngle != -1) replyToIdStr.truncate( rightAngle + 1 ); } else if (strncasecmp(line,"References:",11)==0 && isblank(line[11])) { int leftAngle, rightAngle; referencesStr = QCString(line+12); leftAngle = referencesStr.findRev( '<' ); if (leftAngle != -1) referencesStr = referencesStr.mid( leftAngle ); rightAngle = referencesStr.find( '>' ); if (rightAngle != -1) referencesStr.truncate( rightAngle + 1 ); } else if (strncasecmp(line,"Message-Id:",11)==0 && isblank(line[11])) { int rightAngle; msgIdStr = QCString(line+12); rightAngle = msgIdStr.find( '>' ); if (rightAngle != -1) msgIdStr.truncate( rightAngle + 1 ); } else if (strncasecmp(line,"Date:",5)==0 && isblank(line[5])) { dateStr = QCString(line+6); lastStr = &dateStr; } else if (strncasecmp(line,"From:", 5)==0 && isblank(line[5])) { fromStr = QCString(line+6); lastStr = &fromStr; } else if (strncasecmp(line,"To:", 3)==0 && isblank(line[3])) { toStr = QCString(line+4); lastStr = &toStr; } else if (strncasecmp(line,"Subject:",8)==0 && isblank(line[8])) { subjStr = QCString(line+9); lastStr = &subjStr; } } if (mAutoCreateIndex) { emit statusMsg(i18n("Writing index file")); writeIndex(); } else mHeaderOffset = 0; quiet(FALSE); correctUnreadMsgsCount(); if (kernel->outboxFolder() == this && count() > 0) KMessageBox::queuedMessageBox(0, KMessageBox::Information, i18n("Your outbox contains messages which were " "most likely not created by KMail.\nPlease remove them from there, if you " "don't want KMail to send them.")); if ( parent() ) parent()->manager()->invalidateFolder(kernel->msgDict(), this); return 0; } //----------------------------------------------------------------------------- KMMessage* KMFolderMbox::readMsg(int idx) { KMMessage* msg; unsigned long msgSize; QCString msgText; KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0 && !mi->isMessage()); assert(mStream != 0); msgSize = mi->msgSize(); msgText.resize(msgSize+2); fseek(mStream, mi->folderOffset(), SEEK_SET); fread(msgText.data(), msgSize, 1, mStream); msgText[msgSize] = '\0'; msg = new KMMessage(*mi); msg->fromString(msgText); mMsgList.set(idx,msg); return msg; } //----------------------------------------------------------------------------- QCString& KMFolderMbox::getMsgString(int idx, QCString &mDest) { unsigned long msgSize; KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0); assert(mStream != 0); msgSize = mi->msgSize(); mDest.resize(msgSize+2); fseek(mStream, mi->folderOffset(), SEEK_SET); fread(mDest.data(), msgSize, 1, mStream); mDest[msgSize] = '\0'; return mDest; } //----------------------------------------------------------------------------- DwString KMFolderMbox::getDwString(int idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0); assert(mStream != 0); fseek(mStream, mi->folderOffset(), SEEK_SET); return DwString(mStream, mi->msgSize()); } //----------------------------------------------------------------------------- int KMFolderMbox::addMsg(KMMessage* aMsg, int* aIndex_ret) { if (!canAddMsgNow(aMsg, aIndex_ret)) return 0; bool opened = FALSE; QCString msgText; char endStr[3]; int idx = -1, rc; KMFolder* msgParent; bool editing = false; int growth = 0; /* Then we can also disable it completely, this wastes time, at least for IMAP if (KMFolder::IndexOk != indexStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } */ if (!mStream) { opened = TRUE; rc = open(); kdDebug(5006) << "addMsg-open: " << rc << endl; if (rc) return rc; } // take message out of the folder it is currently in, if any msgParent = aMsg->parent(); if (msgParent) { if (msgParent==this) { if (kernel->folderIsDraftOrOutbox(this)) //special case for Edit message. { kdDebug(5006) << "Editing message in outbox or drafts" << endl; editing = true; } else return 0; } idx = msgParent->find(aMsg); msgParent->getMsg( idx ); } if (protocol() != "imap") { /* QFile fileD0( "testdat_xx-kmfoldermbox-0" ); if( fileD0.open( IO_WriteOnly ) ) { QDataStream ds( &fileD0 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD0.close(); // If data is 0 we just create a zero length file. } */ aMsg->setStatusFields(); /* QFile fileD1( "testdat_xx-kmfoldermbox-1" ); if( fileD1.open( IO_WriteOnly ) ) { QDataStream ds( &fileD1 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD1.close(); // If data is 0 we just create a zero length file. } */ if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by aMsg->removeHeaderField("Content-Type"); // the line above } msgText = aMsg->asString(); msgText.replace(QRegExp("\nFrom "),"\n>From "); size_t len = msgText.length(); assert(mStream != 0); clearerr(mStream); if (len <= 0) { kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; if (opened) close(); return 0; } // Make sure the file is large enough to check for an end // character fseek(mStream, 0, SEEK_END); off_t revert = ftell(mStream); if (ftell(mStream) >= 2) { // write message to folder file fseek(mStream, -2, SEEK_END); fread(endStr, 1, 2, mStream); // ensure separating empty line if (ftell(mStream) > 0 && endStr[0]!='\n') { ++growth; if (endStr[1]!='\n') { //printf ("****endStr[1]=%c\n", endStr[1]); fwrite("\n\n", 1, 2, mStream); ++growth; } else fwrite("\n", 1, 1, mStream); } } fseek(mStream,0,SEEK_END); // this is needed on solaris and others int error = ferror(mStream); if (error) { if (opened) close(); return error; } fprintf(mStream, "From %s %s\n", (const char *)aMsg->fromEmail(), (const char *)aMsg->dateShortStr()); off_t offs = ftell(mStream); fwrite(msgText, len, 1, mStream); if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream); fflush(mStream); size_t size = ftell(mStream) - offs; error = ferror(mStream); if (error) { kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; if (ftell(mStream) > revert) { kdDebug(5006) << "Undoing changes" << endl; truncate( location().local8Bit(), revert ); } kernel->emergencyExit( i18n("Not enough free disk space.") ); /* This code is not 100% reliable bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on device or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kernel->kbp()->busy(); if (opened) close(); kernel->kbp()->idle(); */ return error; } if (msgParent) { if (idx >= 0) msgParent->take(idx); } // if (mAccount) aMsg->removeHeaderField("X-UID"); if (aMsg->status()==KMMsgStatusUnread || aMsg->status()==KMMsgStatusNew || (this == kernel->outboxFolder())) { if (mUnreadMsgs == -1) mUnreadMsgs = 1; else ++mUnreadMsgs; emit numUnreadMsgsChanged( this ); } ++mTotalMsgs; // store information about the position in the folder file in the message aMsg->setParent(this); aMsg->setFolderOffset(offs); aMsg->setMsgSize(size); idx = mMsgList.append(aMsg); if (aMsg->getMsgSerNum() <= 0) aMsg->setMsgSerNum(); // change the length of the previous message to encompass white space added if ((idx > 0) && (growth > 0)) { // don't grow if a deleted message claims space at the end of the file if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() ) mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth ); } // write index entry if desired if (mAutoCreateIndex) { assert(mIndexStream != 0); clearerr(mIndexStream); fseek(mIndexStream, 0, SEEK_END); revert = ftell(mIndexStream); int len; const uchar *buffer = aMsg->asIndexString(len); fwrite(&len,sizeof(len), 1, mIndexStream); aMsg->setIndexOffset( ftell(mIndexStream) ); aMsg->setIndexLength( len ); if(fwrite(buffer, len, 1, mIndexStream) != 1) kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; fflush(mIndexStream); error = ferror(mIndexStream); error |= appendtoMsgDict(idx); if (error) { kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl; if (ftell(mIndexStream) > revert) { kdWarning(5006) << "Undoing changes" << endl; truncate( indexLocation().local8Bit(), revert ); } kernel->emergencyExit( i18n("Not enough free disk space.") ); /* This code may not be 100% reliable bool busy = kernel->kbp()->isBusy(); if (busy) kernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on device or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kernel->kbp()->busy(); if (opened) close(); */ return error; } } // some "paper work" if (aIndex_ret) *aIndex_ret = idx; emitMsgAddedSignals(idx); if (opened) close(); // All streams have been flushed without errors if we arrive here // Return success! // (Don't return status of stream, it may have been closed already.) return 0; } //----------------------------------------------------------------------------- int KMFolderMbox::compact() { QString tempName; QString msgStr; int rc = 0; int openCount = mOpenCount; if (!needsCompact) return 0; if (!mCompactable) { kdDebug(5006) << location() << " compaction skipped." << endl; return 0; } kdDebug(5006) << "Compacting " << idString() << endl; if (KMFolderIndex::IndexOk != indexStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } tempName = path() + "/." + name() + ".compacted"; mode_t old_umask = umask(077); FILE *tmpfile = fopen(tempName.local8Bit(), "w"); umask(old_umask); if (!tmpfile) return errno; open(); KMMsgInfo* mi; size_t msize; off_t folder_offset; off_t offs=0; size_t msgs=0; QCString mtext; for(int idx = 0; idx < mMsgList.count(); idx++) { if(!(msgs++ % 10)) { msgStr = i18n("Compacting folder: %1 messages done").arg(msgs); emit statusMsg(msgStr); } mi = (KMMsgInfo*)mMsgList[idx]; msize = mi->msgSize(); if (mtext.size() < msize + 2) mtext.resize(msize+2); folder_offset = mi->folderOffset(); //now we need to find the separator! grr... for(off_t i = folder_offset-25; TRUE; i -= 20) { off_t chunk_offset = i <= 0 ? 0 : i; if(fseek(mStream, chunk_offset, SEEK_SET) == -1) { rc = errno; break; } if (mtext.size() < 20) mtext.resize(20); fread(mtext.data(), 20, 1, mStream); if(i <= 0) { //woops we've reached the top of the file, last try.. if(!strncasecmp(mtext.data(), "from ", 5)) { if (mtext.size() < folder_offset) mtext.resize(folder_offset); if(fseek(mStream, chunk_offset, SEEK_SET) == -1 || !fread(mtext.data(), folder_offset, 1, mStream) || !fwrite(mtext.data(), folder_offset, 1, tmpfile)) { rc = errno; break; } offs += folder_offset; } else { rc = 666; } break; } else { int last_crlf = -1; for(int i2 = 0; i2 < 20; i2++) { if(*(mtext.data()+i2) == '\n') last_crlf = i2; } if(last_crlf != -1) { int size = folder_offset - (i + last_crlf+1); if ((int)mtext.size() < size) mtext.resize(size); if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 || !fread(mtext.data(), size, 1, mStream) || !fwrite(mtext.data(), size, 1, tmpfile)) { rc = errno; break; } offs += size; break; } } } if (rc) break; //now actually write the message if(fseek(mStream, folder_offset, SEEK_SET) == -1 || !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) { rc = errno; break; } mi->setFolderOffset(offs); offs += msize; } if (!rc) rc = fflush(tmpfile); if (!rc) rc = fsync(fileno(tmpfile)); rc |= fclose(tmpfile); if (!rc) { bool autoCreate = mAutoCreateIndex; ::rename(tempName.local8Bit(), location().local8Bit()); writeIndex(); writeConfig(); mAutoCreateIndex = false; close(TRUE); mAutoCreateIndex = autoCreate; } else { close(); kdDebug(5006) << "Error occurred while compacting" << endl; kdDebug(5006) << location() << endl; kdDebug(5006) << "Compaction aborted." << endl; } if (openCount > 0) { open(); mOpenCount = openCount; } quiet(FALSE); if (!mQuiet) emit changed(); else mChanged = TRUE; needsCompact = false; // We are clean now return 0; } //----------------------------------------------------------------------------- void KMFolderMbox::setLockType( LockType ltype ) { mLockType = ltype; } //----------------------------------------------------------------------------- void KMFolderMbox::setProcmailLockFileName( const QString &fname ) { mProcmailLockFileName = fname; } //----------------------------------------------------------------------------- int KMFolderMbox::removeContents() { int rc = 0; rc = unlink(location().local8Bit()); return rc; } //----------------------------------------------------------------------------- int KMFolderMbox::expungeContents() { int rc = 0; if (truncate(location().local8Bit(), 0)) rc = errno; return rc; } //----------------------------------------------------------------------------- #include "kmfoldermbox.moc"