// kmfolder.cpp // Author: Stefan Taferner #include #include "kmglobal.h" #include "kmfolder.h" #include "kmmessage.h" #include "kmfolderdir.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include #endif #define MAX_LINE 4096 #define INIT_MSGS 8 // Current version of the table of contents (index) files #define INDEX_VERSION 1201 // 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); static int _rename(const char* oldname, const char* newname) { return rename(oldname, newname); } //----------------------------------------------------------------------------- KMFolder :: KMFolder(KMFolderDir* aParent, const char* aName) : KMFolderInherited(aParent, aName), mMsgList(INIT_MSGS) { //-- in case that the compiler has problems with the static version above: //msgSepLen = strlen(MSG_SEPERATOR_START); initMetaObject(); mStream = NULL; mIndexStream = NULL; mOpenCount = 0; mQuiet = 0; mHeaderOffset = 0; mAutoCreateIndex= TRUE; mFilesLocked = FALSE; mIsSystemFolder = FALSE; mType = "plain"; mAcctList = NULL; mDirty = FALSE; } //----------------------------------------------------------------------------- KMFolder :: ~KMFolder() { if (mOpenCount>0) close(TRUE); //if (mAcctList) delete mAcctList; /* Well, this is a problem. If I add the above line then kmfolder depends * on kmaccount and is then not that portable. Hmm. */ } //----------------------------------------------------------------------------- const QString KMFolder::location(void) const { QString sLocation; sLocation = path().copy(); if (!sLocation.isEmpty()) sLocation += '/'; sLocation += name(); return sLocation; } //----------------------------------------------------------------------------- const QString KMFolder::indexLocation(void) const { QString sLocation; sLocation = path().copy(); if (!sLocation.isEmpty()) sLocation += '/'; sLocation += '.'; sLocation += name(); sLocation += ".index"; return sLocation; } //----------------------------------------------------------------------------- int KMFolder::open(void) { int rc = 0; mOpenCount++; if (mOpenCount > 1) return 0; // already open assert(name() != NULL); mStream = fopen(location(), "r+"); // messages file if (!mStream) { debug("Cannot open folder `%s': %s", (const char*)location(), strerror(errno)); return errno; } if (!path().isEmpty()) { if (isIndexOutdated()) // test if contents file has changed { mIndexStream = NULL; warning(nls->translate("Contents of folder `%s' changed.\n" "Recreating the index file."), (const char*)name()); } else mIndexStream = fopen(indexLocation(), "r+"); // index file if (!mIndexStream) rc = createIndexFromContents(); else readIndex(); } else { debug("No path specified for folder `" + name() + "' -- Turning autoCreateIndex off"); mAutoCreateIndex = FALSE; rc = createIndexFromContents(); } if (!rc) lock(); mQuiet = 0; return rc; } //----------------------------------------------------------------------------- int KMFolder::create(void) { int rc; assert(name() != NULL); assert(mOpenCount == 0); mStream = fopen(location(), "w"); if (!mStream) return errno; if (!path().isEmpty()) { mIndexStream = fopen(indexLocation(), "w"); if (!mIndexStream) return errno; } else { debug("Folder `" + name() + "' has no path specified -- turning autoCreateIndex off"); mAutoCreateIndex = FALSE; } mOpenCount++; mQuiet = 0; rc = writeIndex(); if (!rc) lock(); return rc; } //----------------------------------------------------------------------------- void KMFolder::close(bool aForced) { if (mOpenCount <= 0 || !mStream) return; if (mOpenCount > 0) mOpenCount--; if (mOpenCount > 0 && !aForced) return; if (mAutoCreateIndex) { if (mDirty) writeIndex(); else sync(); } unlock(); if (mStream) fclose(mStream); if (mIndexStream) fclose(mIndexStream); mOpenCount = 0; mStream = NULL; mIndexStream = NULL; mFilesLocked = FALSE; mMsgList.reset(INIT_MSGS); } //----------------------------------------------------------------------------- int KMFolder::lock(void) { int rc; assert(mStream != NULL); mFilesLocked = FALSE; rc = fcntl(fileno(mStream), F_SETLK, F_WRLCK); if (rc) return errno; if (mIndexStream >= 0) { rc = fcntl(fileno(mIndexStream), F_SETLK, F_WRLCK); if (rc) { rc = errno; rc = fcntl(fileno(mIndexStream), F_SETLK, F_UNLCK); return rc; } } mFilesLocked = TRUE; debug("Folder `%s' is now locked.", (const char*)location()); return 0; } //----------------------------------------------------------------------------- int KMFolder::unlock(void) { int rc; assert(mStream != NULL); mFilesLocked = FALSE; debug("Unlocking folder `%s'.", (const char*)location()); rc = fcntl(fileno(mStream), F_SETLK, F_UNLCK); if (rc) return errno; if (mIndexStream >= 0) { rc = fcntl(fileno(mIndexStream), F_SETLK, F_UNLCK); if (rc) return errno; } return 0; } //----------------------------------------------------------------------------- bool KMFolder::isIndexOutdated(void) { QFileInfo contInfo(location()); QFileInfo indInfo(indexLocation()); if (!contInfo.exists()) return FALSE; if (!indInfo.exists()) return TRUE; return (contInfo.lastModified() > indInfo.lastModified()); } //----------------------------------------------------------------------------- int KMFolder::createIndexFromContents(void) { char line[MAX_LINE]; char status[8], xstatus[8]; QString subjStr, dateStr, fromStr, xmarkStr; unsigned long offs, size, pos; bool atEof = FALSE; KMMsgInfo* mi; QString msgStr(256); QRegExp regexp(MSG_SEPERATOR_REGEX); int i, num; short needStatus; assert(mStream != NULL); rewind(mStream); mMsgList.clear(); num = -1; offs = 0; size = 0; dateStr = ""; fromStr = ""; subjStr = ""; *status = '\0'; *xstatus = '\0'; xmarkStr = ""; needStatus = 3; while (!atEof) { if ((num & 127) == 0) { msgStr.sprintf(nls->translate("Creating index file: %d messages done"), num); emit statusMsg(msgStr); } pos = ftell(mStream); if (!fgets(line, MAX_LINE, mStream)) atEof = TRUE; if (atEof || (strncmp(line,MSG_SEPERATOR_START, msgSepLen)==0 && regexp.match(line) >= 0)) { size = pos - offs; pos = ftell(mStream); if (num >= 0) { if (size > 0) { mi = new KMMsgInfo(this); mi->init(subjStr, fromStr, 0, KMMsgStatusNew, xmarkStr, offs, size); mi->setStatus(status,xstatus); mi->setDate(dateStr); mi->setDirty(FALSE); mMsgList.append(mi); *status = '\0'; *xstatus = '\0'; needStatus = 3; xmarkStr = ""; dateStr = ""; fromStr = ""; subjStr = ""; } else num--; } offs = ftell(mStream); num++; } else if ((needStatus & 1) && *line=='S' && strncmp(line, "Status: ", 8) == 0) { for(i=0; i<4 && line[i+8] > ' '; i++) status[i] = line[i+8]; status[i] = '\0'; needStatus &= ~1; } else if ((needStatus & 2) && *line=='X' && strncmp(line, "X-Status: ", 10)==0) { for(i=0; i<4 && line[i+10] > ' '; i++) xstatus[i] = line[i+10]; xstatus[i] = '\0'; needStatus &= ~2; } else if (*line=='X' && strncmp(line, "X-KMail-Mark: ", 14) == 0) xmarkStr = QString(line+14).copy(); else if (*line=='D' && strncmp(line, "Date: ", 6) == 0) dateStr = QString(line+6).copy(); else if (*line=='F' && strncmp(line, "From: ", 6) == 0) fromStr = QString(line+6).copy(); else if (*line=='S' && strncmp(line, "Subject: ", 9) == 0) subjStr = QString(line+9).copy(); } if (mAutoCreateIndex) writeIndex(); else mHeaderOffset = 0; return 0; } //----------------------------------------------------------------------------- int KMFolder::writeIndex(void) { KMMsgBase* msgBase; int i=0; if (mIndexStream) fclose(mIndexStream); mIndexStream = fopen(indexLocation(), "w"); if (!mIndexStream) return errno; fprintf(mIndexStream, "# KMail-Index V%d\n", INDEX_VERSION); mHeaderOffset = ftell(mIndexStream); for (i=0; iasIndexString()); } fflush(mIndexStream); mDirty = FALSE; return 0; } //----------------------------------------------------------------------------- void KMFolder::setAutoCreateIndex(bool autoIndex) { mAutoCreateIndex = autoIndex; } //----------------------------------------------------------------------------- bool KMFolder::readIndexHeader(void) { int indexVersion; assert(mIndexStream != NULL); fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion); if (indexVersion < INDEX_VERSION) { debug("Index index file %s is out of date. Re-creating it.", (const char*)indexLocation()); createIndexFromContents(); return FALSE; } return TRUE; } //----------------------------------------------------------------------------- void KMFolder::readIndex(void) { char line[MAX_LINE]; KMMsgInfo* mi; assert(mIndexStream != NULL); rewind(mIndexStream); mMsgList.clear(); if (!readIndexHeader()) return; mDirty = FALSE; mHeaderOffset = ftell(mIndexStream); mMsgList.clear(); while (!feof(mIndexStream)) { fgets(line, MAX_LINE, mIndexStream); if (feof(mIndexStream)) break; mi = new KMMsgInfo(this); mi->fromIndexString(line); if (mi->status() == KMMsgStatusDeleted) { delete mi; // skip messages that are marked as deleted mDirty = TRUE; continue; } else if (mi->status() == KMMsgStatusNew) { mi->setStatus(KMMsgStatusUnread); mi->setDirty(FALSE); } mMsgList.append(mi); } } //----------------------------------------------------------------------------- void KMFolder::quiet(bool beQuiet) { if (beQuiet) mQuiet++; else { mQuiet--; if (mQuiet <= 0) { mQuiet = 0; emit changed(); } } } //----------------------------------------------------------------------------- void KMFolder::removeMsg(KMMsgBasePtr aMsg) { removeMsg(find(aMsg)); } //----------------------------------------------------------------------------- void KMFolder::removeMsg(int idx) { assert(idx>=0); mMsgList.take(idx); mDirty = TRUE; if (!mQuiet) emit msgRemoved(idx); } //----------------------------------------------------------------------------- KMMessage* KMFolder::take(int idx) { KMMsgBase* mb; KMMessage* msg; assert(mStream!=NULL); assert(idx>=0 && idx<=mMsgList.high()); mb = mMsgList[idx]; if (!mb) return NULL; if (!mb->isMessage()) readMsg(idx); msg = (KMMessage*)mMsgList.take(idx); mDirty = TRUE; if (!mQuiet) emit msgRemoved(idx); return msg; } //----------------------------------------------------------------------------- KMMessage* KMFolder::getMsg(int idx) { KMMsgBase* mb; assert(idx>=0 && idx<=mMsgList.high()); mb = mMsgList[idx]; if (!mb) return NULL; if (mb->isMessage()) return ((KMMessage*)mb); return readMsg(idx); } //----------------------------------------------------------------------------- KMMessage* KMFolder::readMsg(int idx) { KMMessage* msg; unsigned long msgSize; QString msgText; KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=NULL && !mi->isMessage()); assert(mStream != NULL); 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; } //----------------------------------------------------------------------------- int KMFolder::moveMsg(KMMessage* aMsg, int* aIndex_ret) { KMFolder* msgParent; int rc; assert(aMsg != NULL); msgParent = aMsg->parent(); if (msgParent) { msgParent->open(); msgParent->take(msgParent->find(aMsg)); msgParent->close(); } open(); rc = addMsg(aMsg, aIndex_ret); close(); return rc; } //----------------------------------------------------------------------------- int KMFolder::addMsg(KMMessage* aMsg, int* aIndex_ret) { long offs, size, len; bool opened = FALSE; QString msgText; int idx; KMFolder* msgParent; if (!mStream) { opened = TRUE; open(); } // take message out of the folder it is currently in, if any msgParent = aMsg->parent(); if (msgParent) { if (msgParent==this) return 0; idx = msgParent->find(aMsg); if (idx >= 0) msgParent->take(idx); } aMsg->setStatusFields(); msgText = aMsg->asString(); len = msgText.length(); assert(mStream != NULL); if (len <= 0) { debug("Message added to folder `%s' contains no data. Ignoring it.", (const char*)name()); if (opened) close(); return 0; } // write message to folder file fseek(mStream, 0, SEEK_END); fwrite("From aaa@aaa Mon Jan 01 00:00:00 1997\n", 38, 1, mStream); offs = ftell(mStream); fwrite(msgText, len, 1, mStream); if (msgText[len-1]!='\n') fwrite("\n", 1, 1, mStream); fflush(mStream); size = ftell(mStream) - offs; // 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); // write index entry if desired if (mAutoCreateIndex) { assert(mIndexStream != NULL); fseek(mIndexStream, 0, SEEK_END); fprintf(mIndexStream, "%s\n", (const char*)aMsg->asIndexString()); fflush(mIndexStream); } // some "paper work" if (aIndex_ret) *aIndex_ret = idx; if (!mQuiet) emit msgAdded(idx); if (opened) close(); return 0; } //----------------------------------------------------------------------------- int KMFolder::rename(const QString aName) { QString oldLoc, oldIndexLoc, newLoc, newIndexLoc, oldName; int rc=0, openCount=mOpenCount; assert(!aName.isEmpty()); oldLoc = location().copy(); oldIndexLoc = indexLocation().copy(); close(TRUE); oldName = name(); setName(aName); newLoc = location(); newIndexLoc = indexLocation(); if (_rename(oldLoc, newLoc)) { setName(oldName); rc = errno; } else if (!oldIndexLoc.isEmpty()) _rename(oldIndexLoc, newIndexLoc); if (openCount > 0) { open(); mOpenCount = openCount; } return rc; } //----------------------------------------------------------------------------- int KMFolder::remove(void) { int rc; assert(name() != NULL); close(TRUE); unlink(indexLocation()); rc = unlink(location()); if (rc) return rc; mMsgList.reset(INIT_MSGS); return 0; } //----------------------------------------------------------------------------- int KMFolder::expunge(void) { int openCount = mOpenCount; assert(name() != NULL); close(TRUE); if (mAutoCreateIndex) truncate(indexLocation(), mHeaderOffset); else unlink(indexLocation()); if (truncate(location(), 0)) return errno; mDirty = FALSE; mMsgList.reset(INIT_MSGS); if (openCount > 0) { open(); mOpenCount = openCount; } if (!mQuiet) emit changed(); return 0; } //----------------------------------------------------------------------------- int KMFolder::compact(void) { KMFolder* tempFolder; KMMessage* msg; QString tempName; int openCount = mOpenCount; tempName = "." + name(); tempName.detach(); tempName += ".compacted"; unlink(tempName); tempFolder = parent()->createFolder(tempName); assert(tempFolder != NULL); quiet(TRUE); tempFolder->open(); open(); while(count() > 0) { msg = getMsg(0); tempFolder->moveMsg(msg); } tempName = tempFolder->location(); tempFolder->close(TRUE); close(TRUE); _rename(tempName, location()); _rename(tempFolder->indexLocation(), indexLocation()); if (openCount > 0) { open(); mOpenCount = openCount; } quiet(FALSE); if (!mQuiet) emit changed(); return 0; } //----------------------------------------------------------------------------- int KMFolder::sync(void) { KMMsgBasePtr mb; unsigned long offset = mHeaderOffset; int i, rc, recSize = KMMsgBase::indexStringLength()+1; int high = mMsgList.high(); if (!mIndexStream) return 0; for (rc=0,i=0; idirty()) { fseek(mIndexStream, offset, SEEK_SET); fprintf(mIndexStream, "%s\n", (const char*)mb->asIndexString()); rc = errno; if (rc) break; mb->setDirty(FALSE); } offset += recSize; } fflush(mIndexStream); mDirty = FALSE; return rc; } //----------------------------------------------------------------------------- void KMFolder::sort(KMMsgList::SortField aField) { mMsgList.sort(aField); if (!mQuiet) emit changed(); mDirty = TRUE; } //----------------------------------------------------------------------------- const char* KMFolder::type(void) const { if (mAcctList) return "In"; return KMFolderInherited::type(); } //----------------------------------------------------------------------------- const QString KMFolder::label(void) const { if (mIsSystemFolder && !mLabel.isEmpty()) return mLabel; return name(); } //----------------------------------------------------------------------------- long KMFolder::countUnread(void) const { int i; long unread; for (i=0, unread=0; istatus()==KMMsgStatusUnread) unread++; } return unread; } //----------------------------------------------------------------------------- void KMFolder::headerOfMsgChanged(const KMMsgBase* aMsg) { int idx = mMsgList.find((KMMsgBasePtr)aMsg); if (idx >= 0 && !mQuiet) emit msgHeaderChanged(idx); } //----------------------------------------------------------------------------- #include "kmfolder.moc"