pjs/lib/libmsg/msgdb.cpp

2173 строки
57 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "msg.h"
#include "xp.h"
#include "xp_time.h"
#include "msgdb.h"
#include "msgdbvw.h"
#include "dberror.h"
#include "grpinfo.h"
#include "thrhead.h"
#include "xpgetstr.h"
#include "newsset.h"
#include "msgdbapi.h"
extern "C"
{
extern int MK_MSG_MARKREAD_COUNT;
extern int MK_MSG_DONE_MARKREAD_COUNT;
}
XP_Bool MessageDB::m_cacheEnabled = TRUE;
MessageDBArray /*NEAR*/ *MessageDB::m_dbCache = NULL;
MessageDBArray::MessageDBArray()
{
}
MessageDB::MessageDB()
{
m_useCount = 0;
m_dbHandle = NULL;
m_headerIndex = 0;
m_addCount = 0;
m_commitChunk = 200;
m_dbName = NULL;
m_dbFolderInfo = NULL;
m_newSet = NULL;
m_folderInfo = NULL;
}
//----------------------------------------------------------------------
// GetDBCache
//----------------------------------------------------------------------
MessageDBArray *
MessageDB::GetDBCache()
{
if (!m_dbCache)
{
m_dbCache = new MessageDBArray();
}
return m_dbCache;
}
void
MessageDB::CleanupCache()
{
if (m_dbCache) // clean up memory leak
{
for (int i = 0; i < GetDBCache()->GetSize(); i++)
{
MessageDB* pMessageDB = GetDBCache()->GetAt(i);
if (pMessageDB)
{
#ifdef DEBUG_bienvenu
XP_Trace("closing %s\n", pMessageDB->m_dbName);
#endif
pMessageDB->ForceClosed();
i--; // back up array index, since closing removes db from cache.
}
}
XP_ASSERT(GetNumInCache() == 0); // better not be any open db's.
delete m_dbCache;
}
m_dbCache = NULL; // Need to reset to NULL since it's a
// static global ptr and maybe referenced
// again in other places.
}
MessageDB::~MessageDB()
{
NotifyAnnouncerGoingAway(NULL);
Purge();
if (m_dbName)
XP_FREE(m_dbName);
if (m_newSet)
delete m_newSet;
}
MsgERR MessageDB::MessageDBOpen(const char * dbName, XP_Bool create)
{
MsgERR err = OpenDB(dbName, create);
if (err == eSUCCESS)
m_useCount++;
return err;
}
MsgERR MessageDB::Close()
{
--m_useCount;
XP_ASSERT(m_useCount >= 0);
if (m_useCount == 0)
{
Purge();
#ifdef DEBUG_bienvenu1
Verify();
#endif
CloseDB();
RemoveFromCache(this);
#ifdef DEBUG_bienvenu
if (GetNumInCache() != 0)
{
XP_Trace("closing %s\n", m_dbName);
DumpCache();
}
#endif
// if this terrifies you, we can make it a static method
delete this;
return(eSUCCESS);
}
else
{
return(eSUCCESS);
}
}
// virtual inlines moved to .cpp file to help compiler charity cases.
MsgERR MessageDB::OnNewPath (const char * /*path*/) { return eSUCCESS; }
// force the database to close - this'll flush out anybody holding onto
// a database without having a listener!
MsgERR MessageDB::ForceClosed()
{
MsgERR err = eSUCCESS;
while (m_useCount > 0 && err == eSUCCESS)
{
int32 saveUseCount = m_useCount;
err = Close();
if (saveUseCount == 1)
break;
}
return err;
}
// this routine should not leave the database open if it returns an error.
MsgERR MessageDB::OpenDB(const char *dbFileName, XP_Bool create)
{
MsgERR err = MSG_OpenDB(dbFileName, create, &m_dbHandle, &m_dbFolderInfoHandle);
XPStringObj newSet;
if (err == eSUCCESS)
{
MSG_DBFolderInfoExchange exchangeInfo;
if (!m_dbFolderInfoHandle)
AddNewFolderInfoToDB();
if (m_dbName)
XP_FREE(m_dbName);
m_dbName = XP_STRDUP( dbFileName );
m_dbFolderInfo = CreateFolderInfo(m_dbFolderInfoHandle);
m_dbFolderInfo->SetHandle(m_dbFolderInfoHandle);
MSG_DBFolderInfo_GetFolderInfo(m_dbFolderInfoHandle, &exchangeInfo);
m_dbFolderInfo->SetExchangeInfo(exchangeInfo);
// compare db version filed out to current db version
if (m_dbFolderInfoHandle != NULL && GetCurVersion() != m_dbFolderInfo->GetDiskVersion())
{
CloseDB();
err = eOldSummaryFile;
}
else
{
m_dbFolderInfo->GetNewArtsSet(newSet);
m_newSet = msg_NewsArtSet::Create(newSet);
}
}
return err;
}
MsgERR MessageDB::CloseDB()
{
if (m_dbHandle != NULL)
{
Commit();
if (m_dbFolderInfo != NULL)
{
delete m_dbFolderInfo;
m_dbFolderInfo = NULL;
}
MSG_CloseDB(m_dbHandle);
}
return eSUCCESS;
}
DBFolderInfo *MessageDB::AddNewFolderInfoToDB()
{
m_dbFolderInfo = CreateFolderInfo(0);
// m_dbFolderInfo->fID = 1; // one and only newsgroup info
m_dbFolderInfo->SetHighWater(0);
m_dbFolderInfo->m_version = GetCurVersion();
// m_dbFolderInfo->setDirty();
MSG_AddDBFolderInfo(m_dbHandle, m_dbFolderInfo->GetHandle());
m_dbFolderInfoHandle = m_dbFolderInfo->GetHandle();
Commit();
return m_dbFolderInfo;
}
// Renames the destdb as sourceDB. DestDB could be open, in which case we need to rename it under
// the covers. SourceDB better not be open, since it's going away.
/*static*/ MsgERR MessageDB::RenameDB(const char *sourceName, const char *destName)
{
MsgERR err = eSUCCESS;
#ifdef DEBUG
{
char* filename = WH_FileName(sourceName, xpMailFolderSummary);
XP_ASSERT(filename);
XP_ASSERT(MessageDB::FindInCache(filename) == NULL);
FREEIF(filename);
}
#endif
MessageDB *destDB;
{
char* filename = WH_FileName(destName, xpMailFolderSummary);
if (!filename) return eOUT_OF_MEMORY;
destDB = MessageDB::FindInCache(filename);
XP_FREE(filename);
}
if (destDB)
err = destDB->CloseDB();
if (XP_FileRename(sourceName, xpMailFolderSummary, destName, xpMailFolderSummary) == 0)
{
if (destDB) {
char* filename = WH_FileName (destName, xpMailFolderSummary);
if (!filename) return eOUT_OF_MEMORY;
err = destDB->OpenDB (filename, FALSE /*create?*/);
XP_FREE(filename);
}
}
else
err = eFAILURE; // ### dmb - need to come up with better error.
return err;
}
MsgERR MessageDB::SetSummaryValid(XP_Bool valid /* = TRUE */)
{
if (!valid)
{
if (m_dbFolderInfo)
{
m_dbFolderInfo->m_version = 0;
// DMB TODO m_dbFolderInfo->setDirty();
Commit();
}
else
return eFAILURE; // can't do this w/o a folder info...
}
// for default db (and news), there's no nothing to set to make it it valid
return eSUCCESS;
}
// returns NULL if not a mail db
MailDB *MessageDB::GetMailDB()
{
return NULL;
}
// returns NULL if not a news db
NewsGroupDB *MessageDB::GetNewsDB()
{
return NULL;
}
DBFolderInfo *MessageDB::CreateFolderInfo(MSG_DBFolderInfoHandle handle)
{
return new DBFolderInfo((handle) ? handle : MSG_CreateMailDBFolderInfo());
}
MSG_FolderInfo *MessageDB::GetFolderInfo()
{
return m_folderInfo;
}
MsgERR MessageDB::GetHeaderFromHandle(MSG_HeaderHandle headerHandle, DBMessageHdr **pResult)
{
DBMessageHdr *header = new DBMessageHdr(headerHandle);
*pResult = header;
return eSUCCESS;
}
DBThreadMessageHdr *MessageDB::GetThreadHeaderFromHandle(MSG_ThreadHandle handle)
{
return (handle) ? new DBThreadMessageHdr(handle) : 0;
}
MsgERR MessageDB::Commit(XP_Bool compress /* = FALSE */)
{
MSG_DBFolderInfoExchange exchangeInfo;
XP_ASSERT(m_dbHandle != NULL);
char *outputLine = (m_newSet) ? m_newSet->Output() : 0;
if (outputLine)
{
GetDBFolderInfo()->SetNewArtsSet(outputLine, m_dbHandle);
delete [] outputLine;
}
else
GetDBFolderInfo()->SetNewArtsSet("", m_dbHandle);
m_dbFolderInfo->GetExchangeInfo(exchangeInfo);
MSG_DBFolderInfo_SetFolderInfo(m_dbFolderInfoHandle, &exchangeInfo, m_dbHandle);
MsgERR err = MSG_CommitDB(m_dbHandle, compress);
if (err != eSUCCESS)
return eWRITE_ERROR;
return eSUCCESS;
}
// Fill in messageHdr from database. Don't change msgHdr if we don't find messageNum.
MsgERR MessageDB::GetMessageHdr(MessageKey messageKey, MessageHdrStruct *msgHdr)
{
DBMessageHdr *headerObject = GetDBHdrForKey(messageKey);
if (headerObject == NULL)
return 0;
else
{
XP_Bool isRead = FALSE;
headerObject->CopyToMessageHdr(msgHdr, GetDB());
// unless/until marking read/unread goes through view layer, we need to get unreadness from newsrc
IsRead(messageKey, &isRead);
if (isRead)
msgHdr->m_flags |= kIsRead;
delete headerObject;
return eSUCCESS;
}
}
MsgERR MessageDB::GetShortMessageHdr(MessageKey messageNum, MSG_MessageLine *msgHdr)
{
MessageHdrStruct messageHdr;
XP_MEMSET(&messageHdr, 0, sizeof(messageHdr));
DBMessageHdr *headerObject = GetDBHdrForKey(messageNum);
if (headerObject == NULL)
{
return eID_NOT_FOUND;
}
else
{
XP_Bool isRead = FALSE;
headerObject->CopyToShortMessageHdr(msgHdr, GetDB());
// unless/until marking read/unread goes through view layer, we need to get unreadness from newsrc
IsRead(messageNum, &isRead);
if (isRead)
msgHdr->flags |= kIsRead;
if (m_newSet && m_newSet->IsMember(headerObject->GetMessageKey()))
msgHdr->flags |= kNew;
delete headerObject;
return eSUCCESS;
}
}
// This function merely takes a DBMessageHdr
// and stores that in m_newHeaders. From there, it will be threaded
// and added to the database in a separate pass.
MsgERR MessageDB::AddHeaderToArray(DBMessageHdr *dbMsgHdr)
{
m_newHeaders.SetAtGrow(m_headerIndex++, dbMsgHdr);
return eSUCCESS;
}
// given an array of message ids, return list header information for each header
MsgERR MessageDB::ListHeaders(MessageKey *pMessageNums,
int numToList,
MessageHdrStruct *pOutput,
int *pNumListed)
{
MsgERR err = eSUCCESS;
*pNumListed = 0;
for (int i = 0; i < numToList; i++)
{
err = GetMessageHdr(pMessageNums[i], &pOutput[i]);
if (err != eSUCCESS)
break;
(*pNumListed)++;
}
return err;
}
MsgERR MessageDB::ListHeadersShort(MessageKey *pMessageNums, int numToList, MSG_MessageLine *pOutput, int *pNumListed)
{
MsgERR err = eSUCCESS;
*pNumListed = 0;
for (int i = 0; i < numToList; i++)
{
err = GetShortMessageHdr(pMessageNums[i], &pOutput[i]);
if (err != eSUCCESS)
break;
(*pNumListed)++;
}
return err;
}
MsgERR MessageDB::ListNextUnread(ListContext **pContext, DBMessageHdr **pResult)
{
DBMessageHdr *pHeader;
MsgERR dbErr = eSUCCESS;
XP_Bool lastWasRead = TRUE;
*pResult = NULL;
while (TRUE)
{
if (*pContext == NULL)
dbErr = ListFirst (pContext, &pHeader);
else
dbErr = ListNext(*pContext, &pHeader);
if (dbErr != eSUCCESS)
{
ListDone(*pContext);
break;
}
// this currently doesn't happen since ListNext doesn't return errors
// other than eDBEndOfList.
else if (dbErr != eSUCCESS)
break;
if (IsHeaderRead(pHeader, &lastWasRead) == eSUCCESS && !lastWasRead)
break;
else
delete pHeader;
}
if (!lastWasRead)
*pResult = pHeader;
return dbErr;
}
MsgERR MessageDB::ListAllIds(IDArray *outputIds)
{
MessageKey *resultKeys;
int32 numKeys;
MsgERR err = MSG_DBHandle_ListAllKeys(m_dbHandle, &resultKeys, &numKeys);
outputIds->SetArray(resultKeys, numKeys, numKeys);
return err;
}
MsgERR MessageDB::ListAllIds(IDArray &outputIds)
{
return ListAllIds(&outputIds);
}
MsgERR MessageDB::ListFirst(ListContext **pContext, DBMessageHdr **pResult)
{
MsgERR err;
err = CreateListIterator(TRUE, pContext, pResult);
if (err == eCorruptDB)
{
err = eEXCEPTION;
SetSummaryValid(FALSE);
}
return err;
}
MsgERR MessageDB::ListNext(ListContext *pContext, DBMessageHdr **pResult)
{
DBMessageHdr *msgHdr = NULL;
MsgERR err;
MSG_HeaderHandle headerHandle;
err = MSG_IteratorHandle_GetNextHeader(pContext->m_iterator, m_dbHandle, &headerHandle);
if (err == eSUCCESS)
err = GetHeaderFromHandle(headerHandle, pResult);
else if (err == eCorruptDB)
SetSummaryValid(FALSE);
return err;
}
MsgERR MessageDB::ListLast(ListContext **pContext, DBMessageHdr **pResult)
{
return CreateListIterator(FALSE, pContext, pResult);
}
// returns the biggest key, kIdNone if none found.
MessageKey MessageDB::ListHighwaterMark()
{
return MSG_DBHandle_GetHighwaterMark(m_dbHandle);
}
// Create an iterator that either starts at the beginning and goes towards the end
// (if forward == TRUE), or starts at the end and goes towards the beginning.
MsgERR MessageDB::CreateListIterator(XP_Bool forward, ListContext **pContext, DBMessageHdr **pResult)
{
MSG_HeaderHandle headerHandle;
ListContext *listContext = NULL;
listContext = new ListContext;
MsgERR err = MSG_DBHandle_CreateHdrListIterator(m_dbHandle, forward, &listContext->m_iterator, &headerHandle);
if (err == eSUCCESS)
err = GetHeaderFromHandle(headerHandle, pResult);
*pContext = listContext;
return err;
}
MsgERR MessageDB::ListDone(ListContext *pContext)
{
if (pContext != NULL)
{
if (pContext->m_iterator)
MSG_IteratorHandle_DestroyIterator(pContext->m_iterator);
delete pContext;
}
return eSUCCESS;
}
/* static */ XP_Bool MessageDB::MatchFlaggedNotOffline(DBMessageHdr *hdr)
{
return (hdr->GetFlags() & kMsgMarked) && !(hdr->GetFlags() & kOffline);
}
void MessageDB::ListMatchingKeys(HdrCompareFunc *compareFunc, IDArray &matchingKeys)
{
MsgERR dbErr;
DBMessageHdr *pHeader;
ListContext *listContext = NULL;
while (TRUE)
{
if (listContext == NULL)
dbErr = ListFirst (&listContext, &pHeader);
else
dbErr = ListNext(listContext, &pHeader);
if (dbErr == eDBEndOfList)
{
dbErr = eSUCCESS;
ListDone(listContext);
break;
}
// this currently doesn't happen since ListNext doesn't return errors
// other than eDBEndOfList.
else if (dbErr != eSUCCESS)
break;
if ((*compareFunc)(pHeader))
matchingKeys.Add(pHeader->GetMessageKey());
delete pHeader;
}
}
// list the ids of the top-level thread ids starting at id == startMsg. This actually returns
// the ids of the first message in each thread.
MsgERR MessageDB::ListThreadIds(MessageKey *startMsg, XP_Bool unreadOnly, MessageKey *pOutput, int32 *pFlags, char *pLevels,
int numToList, int *pNumListed, MessageDBView *view, int32 *pTotalHeaders)
{
MsgERR err = eSUCCESS;
DBMessageHdr *msgHdr;
MSG_ThreadHandle threadHandle = NULL;
DBThreadMessageHdr * threadHdr = NULL;
// N.B..don't ret before assigning numListed to *pNumListed
int numListed = 0;
if (*startMsg > 0)
{
XP_ASSERT(m_threadIterator != NULL); // for now, we'll just have to rely on the caller leaving
// the iterator in the right place.
err = MSG_IteratorHandle_GetNextThread(m_threadIterator, m_dbHandle, &threadHandle);
}
else
{
MSG_DBHandle_CreateThreadListIterator(m_dbHandle, TRUE, &m_threadIterator, &threadHandle);
}
if (!threadHandle)
{
if (*startMsg > 0)
err = eID_NOT_FOUND;
}
else
{
threadHdr = new DBThreadMessageHdr(threadHandle);
int32 threadCount;
int32 threadsRemoved = 0;
for (int i = 0; i < numToList && threadHdr != NULL; i++)
{
MSG_ThreadHandle nextThreadHandle = NULL;
DBThreadMessageHdr *nextThreadHdr = NULL;
err = MSG_IteratorHandle_GetNextThread(m_threadIterator, m_dbHandle, &nextThreadHandle);
if (err == eCorruptDB)
break;
else if (err == eDBEndOfList)
err = eSUCCESS;
if (nextThreadHandle)
nextThreadHdr = new DBThreadMessageHdr(nextThreadHandle);
else
nextThreadHdr = NULL;
if (threadHdr->GetNumChildren() != 0) // not empty thread
{
if (pTotalHeaders)
*pTotalHeaders += threadHdr->GetNumChildren();
if (unreadOnly)
msgHdr = threadHdr->GetFirstUnreadChild(this);
else
msgHdr = threadHdr->GetChildHdrAt(0);
uint32 threadFlags = threadHdr->GetFlags();
if (msgHdr != NULL && (!view || view->WantsThisThread(threadHdr)))
{
pOutput[numListed] = msgHdr->GetMessageKey();
pLevels[numListed] = 0 /* msgHdr->GetLevel() */;
// DMB TODO - This will do for now...Until we decide how to
// handle thread flags vs. message flags, if we do decide
// to make them different.
msgHdr->OrFlags(threadFlags & (kWatched | kIgnored));
XP_Bool isRead = FALSE;
// make sure DB agrees with newsrc, if we're news.
IsRead(msgHdr->GetMessageKey(), &isRead);
MarkHdrRead(msgHdr, isRead, NULL);
// try adding in kIsThread flag for unreadonly view.
if (GetThreadCount(threadHdr, &threadCount) == eSUCCESS && threadCount > 1)
pFlags[numListed] |= kHasChildren;
pFlags[numListed] = msgHdr->m_flags | kIsThread | threadFlags;
numListed++;
}
delete msgHdr;
}
else if (threadsRemoved < 10 && !(threadHdr->GetFlags() & (kWatched | kIgnored)))
{
MSG_DBHandle_RemoveThread(m_dbHandle, threadHandle);
threadsRemoved++; // don't want to remove all empty threads first time
// around as it will choke preformance for upgrade.
#ifdef DEBUG_bienvenu
XP_Trace("removing empty non-ignored non-watched thread\n");
#endif
}
delete threadHdr;
threadHdr = nextThreadHdr;
}
}
if (threadHdr != NULL)
{
*startMsg = threadHdr->GetThreadID();
delete threadHdr;
}
else
{
*startMsg = kIdNone;
MSG_IteratorHandle_DestroyIterator(m_threadIterator);
m_threadIterator = NULL;
}
*pNumListed = numListed;
return err;
}
// helper function to get the thread list context from a thread id and start msg.
// If successful, pThreadHdr will be non null on return
MsgERR MessageDB::GetDBThreadListContext(MessageKey threadId, MessageKey startMsg, DBThreadMessageHdr **pThreadHdr, uint16 *pThreadIndex)
{
*pThreadIndex = 0;
*pThreadHdr = GetDBThreadHdrForThreadID(threadId);
if (*pThreadHdr == NULL)
return eID_NOT_FOUND;
return GetMessageIndexInThread(*pThreadHdr, startMsg, pThreadIndex);
}
MsgERR MessageDB::GetMessageIndexInThread(DBThreadMessageHdr *threadHdr, MessageKey startMsg, uint16 *pThreadIndex)
{
XP_Bool foundStartMsg = FALSE;
if (startMsg != kIdNone && startMsg != 0)
{
while (*pThreadIndex < threadHdr->GetNumChildren())
{
if (threadHdr->GetChildAt(*pThreadIndex) == startMsg)
{
foundStartMsg = TRUE;
break;
}
(*pThreadIndex)++;
}
}
else
{
*pThreadIndex = 0;
foundStartMsg = TRUE;
}
return (foundStartMsg) ? eSUCCESS : eID_NOT_FOUND;
}
// overloaded helper function to get the thread list context from a msgHdr and start msg.
// If successful, pThreadHdr will be non null on return
MsgERR MessageDB::GetDBThreadListContext(DBMessageHdr *msgHdr, MessageKey startMsg, DBThreadMessageHdr **pThreadHdr, uint16 *pThreadIndex)
{
*pThreadIndex = 0;
*pThreadHdr = GetDBThreadHdrForMsgHdr(msgHdr);
if (*pThreadHdr == NULL)
return eID_NOT_FOUND;
return GetMessageIndexInThread(*pThreadHdr, startMsg, pThreadIndex);
}
// Overloaded ListThreadIds which takes a ListContext
MsgERR MessageDB::ListThreadIds(ListContext * /*context*/,
MessageKey * /*pOutput*/,
int /*numToList*/,
int * /*numListed*/)
{
return eNYI;
}
MsgERR MessageDB::ListUnreadIdsInThread(MessageKey threadId, MessageKey *startMsg, XPByteArray &levelStack, int numToList, MessageKey *pOutput, char *pFlags, char *pLevels, int *pNumListed)
{
MsgERR err;
uint16 threadIndex = 0;
DBThreadMessageHdr *threadHdr;
*pNumListed = 0;
err = GetDBThreadListContext(threadId, *startMsg, &threadHdr, &threadIndex);
if (err != eSUCCESS)
return err;
// these children ids are in thread order.
int i;
int startNumListed = *pNumListed;
for (i = 0; i + threadIndex < threadHdr->GetNumChildren() && (*pNumListed - startNumListed) < numToList; i++)
{
DBMessageHdr *msgHdr = threadHdr->GetChildHdrAt(i + threadIndex);
if (msgHdr != NULL)
{
// if the current header's level is <= to the top of the level stack,
// pop off the top of the stack.
// ### dmb unreadonly - The level stack needs to work across calls
// to this routine, in the case that we have more than 200 unread
// messages in a thread.
while (levelStack.GetSize() > 1 &&
msgHdr->GetLevel() <= levelStack.GetAt(levelStack.GetSize() - 1))
{
levelStack.RemoveAt(levelStack.GetSize() - 1);
}
if (! (msgHdr->GetFlags() & kExpunged))
{
XP_Bool isRead = FALSE;
IsRead(msgHdr->GetMessageKey(), &isRead);
if (!isRead)
{
uint8 levelToAdd;
// just make sure flag is right in db.
MarkHdrRead(msgHdr, FALSE, NULL);
*pOutput++ = msgHdr->GetMessageKey(); // was fId DMB TODO
*pFlags = 0;
// pLevels[i] = msgHdr->GetLevel();
if (levelStack.GetSize() == 0)
levelToAdd = 0;
else
levelToAdd = levelStack.GetAt(levelStack.GetSize() - 1) + 1;
*pLevels++ = levelToAdd;
#ifdef DEBUG_bienvenu
// XP_Trace("added at level %d\n", levelToAdd);
#endif
levelStack.Add(levelToAdd);
MessageDBView::CopyDBFlagsToExtraFlags(msgHdr->m_flags, pFlags);
pFlags++;
(*pNumListed)++;
}
}
delete msgHdr;
}
}
if ((i + threadIndex) < threadHdr->GetNumChildren())
*startMsg = threadHdr->GetChildAt(i + threadIndex);
else
*startMsg = kIdNone;
delete threadHdr;
return eSUCCESS;
}
MsgERR MessageDB::ListIdsInThread(DBMessageHdr *msgHdr, MessageKey *startMsg, int numToList, MessageKey *pOutput, char *pFlags, char *pLevels, int *pNumListed)
{
uint16 threadIndex = 0;
DBThreadMessageHdr *threadHdr;
MsgERR err;
err = GetDBThreadListContext(msgHdr, *startMsg, &threadHdr, &threadIndex);
if (err != eSUCCESS)
return err;
return ListIdsInThread(threadHdr, threadIndex, startMsg, numToList, pOutput, pFlags, pLevels, pNumListed);
}
MsgERR MessageDB::ListIdsInThread(MessageKey threadId, MessageKey *startMsg, int numToList, MessageKey *pOutput, char *pFlags, char *pLevels, int *pNumListed)
{
uint16 threadIndex = 0;
DBThreadMessageHdr *threadHdr;
MsgERR err;
*pNumListed = 0;
err = GetDBThreadListContext(threadId, *startMsg, &threadHdr, &threadIndex);
if (err != eSUCCESS)
return err;
return ListIdsInThread(threadHdr, threadIndex, startMsg, numToList, pOutput, pFlags, pLevels, pNumListed);
}
MsgERR MessageDB::ListIdsInThread(DBThreadMessageHdr *threadHdr, uint16 threadIndex, MessageKey *startMsg, int numToList, MessageKey *pOutput, char *pFlags, char *pLevels, int *pNumListed)
{
// these children ids should be in thread order.
int i;
*pNumListed = 0;
for (i = 0; i + threadIndex < threadHdr->GetNumChildren() && i < numToList; i++)
{
DBMessageHdr *msgHdr = threadHdr->GetChildHdrAt(i + threadIndex);
if (msgHdr != NULL)
{
if (! (msgHdr->GetFlags() & kExpunged))
{
XP_Bool isRead = FALSE;
IsRead(msgHdr->GetMessageKey(), &isRead);
// just make sure flag is right in db.
MarkHdrRead(msgHdr, isRead, NULL);
// if (isRead)
// msgHdr->m_flags |= kIsRead;
// else
// msgHdr->m_flags &= ~kIsRead;
*pOutput++ = msgHdr->GetMessageKey();
pFlags[i] = 0;
pLevels[i] = msgHdr->GetLevel();
// turn off thread or elided bit if they got turned on (maybe from new only view?)
if (i > 0)
msgHdr->AndFlags(~(kIsThread|kElided));
MessageDBView::CopyDBFlagsToExtraFlags(msgHdr->m_flags, &pFlags[i]);
(*pNumListed)++;
}
else
{
XP_ASSERT(FALSE); // shouldn't happen - expunging should remove
}
delete msgHdr;
}
}
if ((i + threadIndex) < threadHdr->GetNumChildren())
*startMsg = threadHdr->GetChildAt(i + threadIndex);
else
*startMsg = kIdNone;
delete threadHdr;
return eSUCCESS;
}
MsgERR MessageDB::GetThreadCount(MessageKey messageKey, int32 *pThreadCount)
{
MsgERR ret = eID_NOT_FOUND;
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(messageKey);
if (threadHdr != NULL)
{
ret = GetThreadCount(threadHdr, pThreadCount);
delete threadHdr;
}
return ret;
}
MsgERR MessageDB::GetThreadCount(DBThreadMessageHdr *threadHdr, int32 *pThreadCount)
{
*pThreadCount = threadHdr->GetNumChildren();
return eSUCCESS;
}
MessageKey MessageDB::GetKeyOfFirstMsgInThread(MessageKey key)
{
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(key);
MessageKey firstKeyInThread = kIdNone;
if (threadHdr == NULL)
{
//XP_ASSERT(FALSE); (rb) message not found, deleted already but delete key hit too fast for us
return firstKeyInThread;
}
// ### dmb UnreadOnly - this is wrong.
firstKeyInThread = threadHdr->GetChildAt(0);
delete threadHdr;
return firstKeyInThread;
}
XP_Bool MessageDB::SetPriority(MessageKey key, MSG_PRIORITY priority)
{
XP_Bool ret;
DBMessageHdr *msgHdr = GetDBHdrForKey(key);
if (msgHdr == NULL)
return FALSE;
ret = SetPriority(msgHdr, priority);
delete msgHdr;
return ret;
}
XP_Bool MessageDB::SetPriority(DBMessageHdr *msgHdr, MSG_PRIORITY priority)
{
if ((GetMailDB() != NULL))
{
msgHdr->SetPriority(priority);
// ###dmb calling SetHdrFlag (on mailDB) this way will basically flush the new mozilla
// status which is all we want. Should invent new method. Also, we should only
// call this on maildbs, because other db's will leave dirty flag set.
SetHdrFlag(msgHdr, TRUE, (MsgFlags) 0);
// ###tw When we decide what our priority header and format will be,
// this code should also fix the header in the mail msg,
// just to be extra paranoid.
return TRUE;
}
else
{
return FALSE;
}
}
// Helper routine - lowest level of flag setting
void MessageDB::SetHdrFlag(DBMessageHdr *msgHdr, XP_Bool bSet, MsgFlags flag)
{
XP_ASSERT(! (flag & kDirty)); // this won't do the right thing so don't.
if (bSet && (!(msgHdr->GetFlags() & flag)))
{
msgHdr->OrFlags(flag | kDirty);
}
else if (!bSet && (msgHdr->GetFlags() & flag))
{
msgHdr->AndFlags(~flag);
msgHdr->OrFlags(kDirty);
}
}
void MessageDB::MarkHdrReadInDB(DBMessageHdr *msgHdr, XP_Bool bRead,
ChangeListener *instigator)
{
SetHdrFlag(msgHdr, bRead, kIsRead);
if (m_newSet)
m_newSet->Remove(msgHdr->GetMessageKey());
if (m_dbFolderInfo != NULL)
{
if (bRead)
m_dbFolderInfo->ChangeNumNewMessages(-1);
else
m_dbFolderInfo->ChangeNumNewMessages(1);
// DMB TODO m_dbFolderInfo->setDirty();
}
NotifyKeyChangeAll(msgHdr->GetMessageKey(), msgHdr->GetFlags(), instigator);
}
MsgERR MessageDB::MarkRead(MessageKey messageKey, XP_Bool bRead,
ChangeListener *instigator)
{
MsgERR err;
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr == NULL)
return eID_NOT_FOUND;
err = MarkHdrRead(msgHdr, bRead, instigator);
delete msgHdr;
return err;
}
MsgERR MessageDB::MarkReplied(MessageKey messageKey, XP_Bool bReplied,
ChangeListener *instigator /* = NULL */)
{
return SetKeyFlag(messageKey, bReplied, kReplied, instigator);
}
MsgERR MessageDB::MarkForwarded(MessageKey messageKey, XP_Bool bForwarded,
ChangeListener *instigator /* = NULL */)
{
return SetKeyFlag(messageKey, bForwarded, kForwarded, instigator);
}
MsgERR MessageDB::MarkHasAttachments(MessageKey messageKey, XP_Bool bHasAttachments,
ChangeListener *instigator)
{
return SetKeyFlag(messageKey, bHasAttachments, kHasAttachment, instigator);
}
MsgERR MessageDB::MarkMarked(MessageKey messageKey, XP_Bool mark,
ChangeListener *instigator)
{
return SetKeyFlag(messageKey, mark, kMsgMarked, instigator);
}
MsgERR MessageDB::MarkOffline(MessageKey messageKey, XP_Bool offline,
ChangeListener *instigator)
{
return SetKeyFlag(messageKey, offline, kOffline, instigator);
}
MsgERR MessageDB::MarkImapDeleted(MessageKey messageKey, XP_Bool deleted,
ChangeListener *instigator)
{
return SetKeyFlag(messageKey, deleted, kIMAPdeleted, instigator);
}
MsgERR MessageDB::MarkMDNNeeded(MessageKey messageKey, XP_Bool bNeeded,
ChangeListener *instigator /* = NULL */)
{
return SetKeyFlag(messageKey, bNeeded, kMDNNeeded, instigator);
}
MsgERR MessageDB::IsMDNNeeded(MessageKey messageKey, XP_Bool *pNeeded)
{
MsgERR err = eSUCCESS;
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr != NULL && pNeeded)
{
*pNeeded = ((msgHdr->GetFlags() & kMDNNeeded) == kMDNNeeded);
delete msgHdr;
return err;
}
else
{
return eID_NOT_FOUND;
}
}
MsgERR MessageDB::MarkMDNSent(MessageKey messageKey, XP_Bool bSent,
ChangeListener *instigator /* = NULL */)
{
return SetKeyFlag(messageKey, bSent, kMDNSent, instigator);
}
MsgERR MessageDB::IsMDNSent(MessageKey messageKey, XP_Bool *pSent)
{
MsgERR err = eSUCCESS;
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr != NULL && pSent)
{
*pSent = msgHdr->GetFlags() & kMDNSent;
delete msgHdr;
return err;
}
else
{
return eID_NOT_FOUND;
}
}
MsgERR MessageDB::SetKeyFlag(MessageKey messageKey, XP_Bool set, int32 flag,
ChangeListener *instigator)
{
MsgERR err = eSUCCESS;
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr == NULL)
return eID_NOT_FOUND;
SetHdrFlag(msgHdr, set, flag);
NotifyKeyChangeAll(msgHdr->GetMessageKey(), msgHdr->GetFlags(), instigator);
delete msgHdr;
return err;
}
MsgERR MessageDB::MarkHdrRead(DBMessageHdr *msgHdr, XP_Bool bRead,
ChangeListener *instigator)
{
XP_Bool isRead;
IsHeaderRead(msgHdr, &isRead);
// if the flag is already correct in the db, don't change it
if (!!isRead != !!bRead)
{
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(msgHdr->GetMessageKey());
if (threadHdr != NULL)
{
threadHdr->MarkChildRead(bRead, m_dbHandle);
delete threadHdr;
}
MarkHdrReadInDB(msgHdr, bRead, instigator);
}
return eSUCCESS;
}
MsgERR MessageDB::MarkAllRead(MWContext *context, IDArray *thoseMarked)
{
MsgERR dbErr;
DBMessageHdr *pHeader;
ListContext *listContext = NULL;
int32 numChanged = 0;
char msgBuf[100];
const char * msgTemplate = XP_GetString(MK_MSG_MARKREAD_COUNT);
while (TRUE)
{
dbErr = ListNextUnread(&listContext, &pHeader);
if (dbErr == eDBEndOfList)
{
dbErr = eSUCCESS;
break;
}
// this currently doesn't happen since ListNext doesn't return errors
// other than eDBEndOfList.
else if (dbErr != eSUCCESS || !pHeader)
break;
if (numChanged % 10 == 0)
{
PR_snprintf (msgBuf, sizeof(msgBuf), msgTemplate, numChanged);
FE_Progress (context, msgBuf);
}
if (thoseMarked)
thoseMarked->Add(pHeader->GetMessageKey());
dbErr = MarkHdrRead(pHeader, TRUE, NULL); // ### dmb - blow off error?
delete pHeader;
if (numChanged++ % 200 == 0) // commit every once in a while
Commit();
}
// force num new to 0.
m_dbFolderInfo->ChangeNumNewMessages(-m_dbFolderInfo->GetNumNewMessages());
// DMB TODO m_dbFolderInfo->setDirty();
msgTemplate = XP_GetString(MK_MSG_DONE_MARKREAD_COUNT);
PR_snprintf (msgBuf, sizeof(msgBuf), msgTemplate, numChanged);
FE_Progress (context, msgBuf);
return dbErr;
}
MsgERR MessageDB::MarkReadByDate (time_t startDate, time_t endDate, MWContext *context, IDArray *markedIds)
{
MsgERR dbErr;
DBMessageHdr *pHeader;
ListContext *listContext = NULL;
int32 numChanged = 0;
char msgBuf[100];
const char * msgTemplate = XP_GetString(MK_MSG_MARKREAD_COUNT);
while (TRUE)
{
if (listContext == NULL)
dbErr = ListFirst (&listContext, &pHeader);
else
dbErr = ListNext(listContext, &pHeader);
if (dbErr == eDBEndOfList)
{
dbErr = eSUCCESS;
ListDone(listContext);
break;
}
// this currently doesn't happen since ListNext doesn't return errors
// other than eDBEndOfList.
else if (dbErr != eSUCCESS)
break;
time_t headerDate = pHeader->GetDate();
if (headerDate > startDate && headerDate <= endDate)
{
XP_Bool isRead;
IsRead(pHeader->GetMessageKey(), &isRead);
if (!isRead)
{
if (markedIds)
markedIds->Add(pHeader->GetMessageKey());
MarkHdrRead(pHeader, TRUE, NULL); // ### dmb - blow off error?
if (numChanged % 10 == 0)
{
PR_snprintf (msgBuf, sizeof(msgBuf), msgTemplate, numChanged);
FE_Progress (context, msgBuf);
}
if (numChanged++ % 1000 == 0) // commit every once in a while
Commit();
}
}
delete pHeader;
}
msgTemplate = XP_GetString(MK_MSG_DONE_MARKREAD_COUNT);
PR_snprintf (msgBuf, sizeof(msgBuf), msgTemplate, numChanged);
FE_Progress (context, msgBuf);
return dbErr;
}
MsgERR MessageDB::MarkLater(MessageKey messageKey, time_t until)
{
XP_ASSERT(m_dbFolderInfo);
if (m_dbFolderInfo != NULL)
{
m_dbFolderInfo->AddLaterKey(messageKey, until);
}
return eSUCCESS;
}
void MessageDB::ClearNewList(XP_Bool notify /* = FALSE */)
{
if (m_newSet)
{
if (notify) // need to update view
{
int32 firstMember;
while ((firstMember = m_newSet->GetFirstMember()) != 0)
{
m_newSet->Remove(firstMember); // this bites, since this will cause us to regen new list many times.
DBMessageHdr *msgHdr = GetDBHdrForKey(firstMember);
if (msgHdr != NULL)
{
NotifyKeyChangeAll(msgHdr->GetMessageKey(), msgHdr->GetFlags(), NULL);
delete msgHdr;
}
}
}
delete m_newSet;
m_newSet = NULL;
}
}
XP_Bool MessageDB::HasNew()
{
return m_newSet && m_newSet->getLength() > 0;
}
MessageKey MessageDB::GetFirstNew()
{
// even though getLength is supposedly for debugging only, it's the only
// way I can tell if the set is empty (as opposed to having a member 0.
if (HasNew())
return m_newSet->GetFirstMember();
else
return MSG_MESSAGEKEYNONE;
}
MessageKey MessageDB::GetUnusedFakeId()
{
ListContext *listContext = NULL;
DBMessageHdr *highHdr = NULL;
MessageKey fakeMsgKey = kIdStartOfFake;
if (ListLast(&listContext, &highHdr) == eSUCCESS)
{
MessageKey curKey = highHdr->GetMessageKey();
while (curKey == fakeMsgKey || curKey == kIdNone || curKey == kIdPending)
{
if (curKey == fakeMsgKey) fakeMsgKey--;
delete highHdr;
highHdr = NULL;
if (ListNext(listContext, &highHdr) == eSUCCESS)
curKey = highHdr->GetMessageKey();
else
break;
}
if (highHdr)
delete highHdr;
ListDone(listContext);
}
return fakeMsgKey;
}
MsgERR MessageDB::GetUnreadKeyInThread(MessageKey threadId, MessageKey *resultKey,
MessageKey *resultThreadId)
{
MsgERR err = eSUCCESS;
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(threadId);
if (threadHdr == NULL)
{
#ifdef DEBUG_bienvenu
XP_ASSERT(FALSE);
#endif
return eID_NOT_FOUND;
}
if (threadHdr->GetNumNewChildren() > 0)
{
MessageKey startMsg = kIdNone;
int numListed;
do
{
const int listChunk = 200;
MessageKey listIDs[listChunk];
char listFlags[listChunk];
char listLevels[listChunk];
err = ListIdsInThread(threadHdr->GetThreadID(), &startMsg, listChunk,
listIDs, listFlags, listLevels, &numListed);
// start at 1, because id 0 is the thread header itself.
for (int i = 1; i < numListed; i++)
{
if (!(listFlags[i] & kIsRead))
{
*resultKey = listIDs[i];
if (resultThreadId)
*resultThreadId = threadId;
break;
}
}
if (numListed < listChunk || startMsg == kIdNone)
break;
}
while (err == eSUCCESS && (*resultKey == kIdNone));
}
delete threadHdr;
return err;
}
MsgERR MessageDB::DeleteMessages(IDArray &messageKeys, ChangeListener *instigator)
{
MsgERR err = eSUCCESS;
for (uint index = 0; index < messageKeys.GetSize(); index++)
{
MessageKey messageKey = messageKeys.GetAt(index);
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr == NULL)
{
err = eID_NOT_FOUND;
break;
}
err = DeleteHeader(msgHdr, instigator, index % 300 == 0);
delete msgHdr;
if (err != eSUCCESS)
break;
}
Commit();
return err;
}
XP_Bool MessageDB::AllMessageKeysImapDeleted(const IDArray &messageKeys)
{
XP_Bool allDeleted = TRUE;
for (uint index = 0; allDeleted && (index < messageKeys.GetSize()); index++)
{
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKeys.GetAt(index));
allDeleted = msgHdr && ((msgHdr->GetFlags() & kIMAPdeleted) != 0);
delete msgHdr;
}
return allDeleted;
}
MsgERR MessageDB::DeleteMessage(MessageKey messageKey, ChangeListener *instigator, XP_Bool commit)
{
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr == NULL)
return eID_NOT_FOUND;
MsgERR ret = DeleteHeader(msgHdr, instigator, commit);
delete msgHdr;
return ret;
}
MsgERR MessageDB::DeleteHeader(DBMessageHdr *msgHdr, ChangeListener *instigator, XP_Bool commit, XP_Bool /* onlyRemoveFromThread */)
{
MessageKey messageKey = msgHdr->GetMessageKey();
// only need to do this for mail - will this speed up news expiration? Is dirtying objects
// we're about to delete slow for ? But are we short circuiting some
// notifications that we need?
// if (GetMailDB())
SetHdrFlag(msgHdr, TRUE, kExpunged); // tell mailbox (mail)
if (m_newSet) // if it's in the new set, better get rid of it.
m_newSet->Remove(msgHdr->GetMessageKey());
if (m_dbFolderInfo != NULL)
{
XP_Bool isRead;
m_dbFolderInfo->ChangeNumMessages(-1);
m_dbFolderInfo->ChangeNumVisibleMessages(-1);
IsRead(msgHdr->GetMessageKey(), &isRead);
if (!isRead)
m_dbFolderInfo->ChangeNumNewMessages(-1);
m_dbFolderInfo->m_expunged_bytes += msgHdr->GetByteLength();
// DMB TODO m_dbFolderInfo->setDirty();
}
NotifyKeyChangeAll(messageKey, msgHdr->GetFlags(), instigator); // tell listeners
// if (!onlyRemoveFromThread) // to speed up expiration, try this. But really need to do this in RemoveHeaderFromDB
RemoveHeaderFromDB(msgHdr);
if (commit)
Commit(); // ### dmb is this a good time to commit?
return eSUCCESS;
}
MsgERR MessageDB::UndoDelete(DBMessageHdr *msgHdr)
{
MsgERR msgErr = AddHdrToDB(msgHdr, NULL, TRUE);
// make sure message is undeleted from source mail folder.
// Need to pretend that it's deleted first to reverse it.
msgHdr->OrFlags(kExpunged);
SetHdrFlag(msgHdr, FALSE, kExpunged);
if (m_dbFolderInfo)
{
m_dbFolderInfo->m_expunged_bytes -= msgHdr->GetByteLength();
// DMB TODO m_dbFolderInfo->setDirty();
}
return msgErr;
}
// This is a lower level routine which doesn't send notifcations or
// update folder info. One use is when a rule fires moving a header
// from one db to another, to remove it from the first db.
void MessageDB::RemoveHeaderFromDB(DBMessageHdr *msgHdr)
{
// DMB TODO
// if (msgHdr->fMark == 0) // msghdr is not in DB!
// return;
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(msgHdr->GetMessageKey());
if (threadHdr != NULL)
{
threadHdr->RemoveChild(msgHdr->GetMessageKey(), m_dbHandle);
// remove empty thread object if it isn't watched or ignored
if (threadHdr->GetNumChildren() == 0 && !(threadHdr->GetFlags() & (kWatched | kIgnored)))
MSG_DBHandle_RemoveThread(m_dbHandle, threadHdr->GetHandle());
delete threadHdr;
}
MSG_DBHandle_RemoveHeader(m_dbHandle, msgHdr->GetHandle());
}
MsgERR MessageDB::MarkThreadIgnored(DBThreadMessageHdr *threadHdr, MessageKey messageKey, XP_Bool bIgnored,
ChangeListener *instigator)
{
if (bIgnored)
{
threadHdr->OrFlags(kIgnored);
threadHdr->AndFlags(~kWatched); // ignore is implicit un-watch
}
else
threadHdr->AndFlags(~kIgnored);
NotifyKeyChangeAll(messageKey, threadHdr->GetFlags(), instigator);
return eSUCCESS;
}
MsgERR MessageDB::MarkThreadWatched(DBThreadMessageHdr *threadHdr, MessageKey messageKey, XP_Bool bWatched,
ChangeListener *instigator)
{
if (bWatched)
{
threadHdr->AndFlags(~kIgnored);
threadHdr->OrFlags(kWatched);
}
else
threadHdr->AndFlags(~kWatched);
NotifyKeyChangeAll(messageKey, threadHdr->GetFlags(), instigator);
return eSUCCESS;
}
MsgERR MessageDB::IsMarked(MessageKey messageKey, XP_Bool *pMarked)
{
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr != NULL)
{
*pMarked = (msgHdr->GetFlags() & kMsgMarked) != 0;
delete msgHdr;
return eSUCCESS;
}
else
{
return eID_NOT_FOUND;
}
}
MsgERR MessageDB::IsRead(MessageKey messageKey, XP_Bool *pRead)
{
MsgERR err = eSUCCESS;
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr != NULL)
{
err = IsHeaderRead(msgHdr, pRead);
delete msgHdr;
return err;
}
else
{
return eID_NOT_FOUND;
}
}
uint32 MessageDB::GetStatusFlags(DBMessageHdr *msgHdr)
{
uint32 statusFlags = msgHdr->GetFlags();
XP_Bool isRead;
if (m_newSet && m_newSet->IsMember(msgHdr->GetMessageKey()))
statusFlags |= kNew;
if (IsRead(msgHdr->GetMessageKey(), &isRead) == eSUCCESS && isRead)
statusFlags |= kIsRead;
return statusFlags;
}
MsgERR MessageDB::IsHeaderRead(DBMessageHdr *hdr, XP_Bool *pRead)
{
if (!hdr)
return eID_NOT_FOUND;
*pRead = (hdr->GetFlags() & kIsRead) != 0;
return eSUCCESS;
}
MsgERR MessageDB::IsIgnored(MessageKey messageKey, XP_Bool *pIgnored)
{
XP_ASSERT(pIgnored != NULL);
if (!pIgnored)
return eBAD_PARAMETER;
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(messageKey);
// This should be very surprising, but we leave that up to the caller
// to determine for now.
if (threadHdr == NULL)
return eID_NOT_FOUND;
*pIgnored = (threadHdr->GetFlags() & kIgnored) ? TRUE : FALSE;
delete threadHdr;
return eSUCCESS;
}
MsgERR MessageDB::HasAttachments(MessageKey messageKey, XP_Bool *pHasThem)
{
XP_ASSERT(pHasThem != NULL);
if (!pHasThem)
return eBAD_PARAMETER;
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgID(messageKey);
// This should be very surprising, but we leave that up to the caller
// to determine for now.
if (threadHdr == NULL)
return eID_NOT_FOUND;
*pHasThem = (threadHdr->GetFlags() & kHasAttachment) ? TRUE : FALSE;
delete threadHdr;
return eSUCCESS;
}
// This function goes through the list of latered documents and marks them
// unread if the current date/time is > than the latered "until" setting.
// Since that is currently always 0, this routine should mark everything
// in the latered list unread.
void MessageDB::HandleLatered()
{
time_t curTime = XP_TIME();
if (!m_dbFolderInfo)
return;
for (int32 laterIndex = 0; laterIndex < m_dbFolderInfo->GetNumLatered(); )
{
time_t until;
MessageKey laterKey = m_dbFolderInfo->GetLateredAt(laterIndex, &until);
if (curTime > until)
{
MarkRead(laterKey, FALSE, NULL);
m_dbFolderInfo->RemoveLateredAt(laterIndex);
}
else
{
laterIndex++;
}
}
}
void MessageDB::SetSortInfo(SortType sortType, SortOrder sortOrder)
{
if (m_dbFolderInfo)
m_dbFolderInfo->SetSortInfo(sortType, sortOrder);
}
MsgERR MessageDB::GetSortInfo(SortType *pSortType, SortOrder *pSortOrder)
{
if (!(pSortType && pSortOrder && m_dbFolderInfo))
{
XP_ASSERT(FALSE);
return eBAD_PARAMETER;
}
m_dbFolderInfo->GetSortInfo(pSortType, pSortOrder);
return eSUCCESS;
}
// Get a handle for a document given its message number. Because a subclass
// of MsgDocument may be returned, we need to return a pointer to an allocated object.
// For some reason, the UI has decided to force a purge of the database.
MsgERR MessageDB::Purge()
{
FinishAddingHeaders(); // this will add m_newHeaders to the db
return eSUCCESS;
}
//-----------------------------------------------------------------------------
// EnableCache
//-----------------------------------------------------------------------------
XP_Bool MessageDB::EnableCache(XP_Bool enable)
{
XP_Bool oldVal = m_cacheEnabled;
m_cacheEnabled = enable;
return(oldVal);
}
//----------------------------------------------------------------------
// FindInCache
//----------------------------------------------------------------------
MessageDB* MessageDB::FindInCache(const char * pDbName)
{
for (int i = 0; i < GetDBCache()->GetSize(); i++)
{
MessageDB* pMessageDB = GetDBCache()->GetAt(i);
if (pMessageDB->MatchDbName(pDbName))
{
return(pMessageDB);
}
}
return(NULL);
}
//----------------------------------------------------------------------
// FindInCache
//----------------------------------------------------------------------
int MessageDB::FindInCache(MessageDB* pMessageDB)
{
for (int i = 0; i < GetDBCache()->GetSize(); i++)
{
if (GetDBCache()->GetAt(i) == pMessageDB)
{
return(i);
}
}
return(-1);
}
//----------------------------------------------------------------------
// RemoveFromCache
//----------------------------------------------------------------------
void MessageDB::RemoveFromCache(MessageDB* pMessageDB)
{
int i = FindInCache(pMessageDB);
if (i != -1)
{
GetDBCache()->RemoveAt(i);
}
}
#ifdef DEBUG
void MessageDB::DumpCache()
{
for (int i = 0; i < GetDBCache()->GetSize(); i++)
{
#ifdef DEBUG_bienvenu
MessageDB* pMessageDB =
#endif
GetDBCache()->GetAt(i);
#ifdef DEBUG_bienvenu
XP_Trace("db %s in cache use count = %d\n", pMessageDB->m_dbName, pMessageDB->m_useCount);
#endif
}
}
#endif
XP_Bool MessageDB::MatchDbName(const char * dbName) // returns TRUE if they match
{
XP_ASSERT(m_dbName);
return !XP_FILENAMECMP(dbName, m_dbName);
}
MsgERR MessageDB::FinishAddingHeaders()
{
MsgERR err = eSUCCESS;
XP_Bool isNewThread;
// go through the new headers adding them to the db
// The idea here is that m_headers is just the new headers
for (int i = 0; i < m_newHeaders.GetSize(); i++)
{
DBMessageHdr *dbMsgHdr = (DBMessageHdr *) m_newHeaders[i];
err = AddHdrToDB(dbMsgHdr, &isNewThread);
delete dbMsgHdr;
}
m_headerIndex = 0;
m_newHeaders.RemoveAll();
return err;
// m_headerIndex = 0;
// return eSUCCESS;
}
DBThreadMessageHdr *MessageDB::GetDBThreadHdrForSubject(DBMessageHdr *msgHdr)
{
MSG_ThreadHandle threadHandle = MSG_DBHandle_GetThreadHandleForMsgHdrSubject(m_dbHandle, msgHdr->GetHandle());
return GetThreadHeaderFromHandle(threadHandle);
}
// This functions takes a string message id and returns the
// corresponding message hdr
DBThreadMessageHdr *MessageDB::GetDBMsgHdrForReference(const char * msgID)
{
DBMessageHdr *headerObject = GetDBMessageHdrForID(msgID);
DBThreadMessageHdr *thread = NULL;
if (headerObject != NULL)
{
// find thread header for header whose message id we matched.
thread = GetDBThreadHdrForMsgID(headerObject->GetMessageKey());
delete headerObject;
}
return thread;
}
// make the passed in header a thread header
MsgERR MessageDB::AddThread(DBMessageHdr *msgHdr)
{
//TRACE("entering AddThread\n");
MSG_ThreadHandle threadHandle = MSG_DBHandle_AddThreadFromMsgHandle(m_dbHandle, msgHdr->GetHandle());
DBThreadMessageHdr *threadHdr = new DBThreadMessageHdr(threadHandle);
AddToThread(msgHdr, threadHdr, FALSE);
XP_ASSERT(threadHdr->GetThreadID() == msgHdr->GetThreadId());
delete threadHdr;
// If this header has references, we might want to create an expired
// header for this thread, instead of promoting this header to thread status.
// In particular, the real thread header might arrive later, in which case
// we would just turn off the expired bit on the dummy. Of course, it
// could be anyone of the parents, and is more likely to be an immediate ancestor...
// which would argue for making the message-id be the last reference, not the first.
// But for now, just make it a top-level thread - we can always rearrange things
// if a better top-level thread header comes in. Or if we decide to have a dummy
// header...
// AddHdr(msgHdr);
//TRACE("adding thread %s\n", (const char *) dummyHdr->m_subject);
#ifdef _DEBUG1 // check that we can pull it out of the database.
DBThreadMessageHdr *newHdr;
newHdr = GetDBMsgHdrForReference(dummyHdr->m_messageId);
ASSERT(newHdr != NULL);
delete newHdr;
#endif
//TRACE("leaving AddThread\n");
return eSUCCESS;
}
// really add it to DB.
MsgERR MessageDB::AddHdr(DBMessageHdr *hdr)
{
// TODO - need to do exception handling.
MSG_DBHandle_AddHeader(m_dbHandle, hdr->GetHandle());
return eSUCCESS;
}
void MessageDB::AddToNewList(MessageKey key)
{
if (m_newSet == NULL)
m_newSet = msg_NewsArtSet::Create();
if (m_newSet)
m_newSet->Add(key);
}
// add a header to the database, and thread it.
// For now, it's OK if newThread or resultHdr are NULL
MsgERR MessageDB::AddHdrToDB(DBMessageHdr *newHdr, XP_Bool *newThread,
XP_Bool notify /* = FALSE */)
{
MsgERR err = eSUCCESS;
DBThreadMessageHdr *refHdr = NULL;
if (m_addCount >= m_commitChunk)
{
Commit();
m_addCount = 0;
}
if (newHdr == NULL)
return err;
#define SUBJ_THREADING 1// try reference threading first
for (int32 i = 0; i < newHdr->GetNumReferences(); i++)
{
MSG_ThreadHandle refHdrThreadHandle = MSG_HeaderHandle_GetThreadForReference(newHdr->GetHandle(), i, m_dbHandle);
if (refHdrThreadHandle)
{
refHdr = GetThreadHeaderFromHandle(refHdrThreadHandle);
if (refHdr)
{
newHdr->SetThreadId(refHdr->GetThreadID());
err = AddToThread(newHdr, refHdr, TRUE);
}
break;
}
}
#ifdef SUBJ_THREADING
// try subject threading if we couldn't find a reference and the subject starts with Re:
if ((newHdr->GetFlags() & kHasRe) && refHdr == NULL && (refHdr = GetDBThreadHdrForSubject(newHdr)) != NULL)
{
newHdr->SetThreadId(refHdr->GetThreadID());
//TRACE("threading based on subject %s\n", (const char *) msgHdr->m_subject);
// AddHdr(newHdr);
// if we move this and do subject threading after, ref threading,
// don't thread within children, since we know it won't work. But for now, pass TRUE.
err = AddToThread(newHdr, refHdr, TRUE);
}
#endif // SUBJ_THREADING
XP_ASSERT( newHdr != NULL);
if (refHdr == NULL)
{
// couldn't find any parent articles - msgHdr is top-level thread, for now
err = AddThread(newHdr);
if (newThread)
*newThread = TRUE;
}
else
{
if (newThread)
*newThread = FALSE;
}
delete refHdr;
// update
if (err == eSUCCESS)
{
if ((newHdr->GetFlags() & kNew))
{
newHdr->AndFlags(~kNew); // make sure not filed out
AddToNewList(newHdr->GetMessageKey());
}
if (m_dbFolderInfo != NULL)
{
m_dbFolderInfo->ChangeNumMessages(1);
m_dbFolderInfo->ChangeNumVisibleMessages(1);
if (! (newHdr->GetFlags() & kIsRead))
m_dbFolderInfo->ChangeNumNewMessages(1);
// dmb todo m_dbFolderInfo->setDirty();
}
if (notify)
NotifyKeyChangeAll(newHdr->GetMessageKey(), newHdr->GetFlags() | kAdded, NULL);
}
m_addCount++;
return err;
}
MsgERR MessageDB::AddToThread(DBMessageHdr *reply, DBThreadMessageHdr *threadHdr, XP_Bool threadInThread)
{
reply->SetLevel(0); // for now, until we get threading within a thread.
reply->SetThreadId(threadHdr->GetThreadID());
// determine where to add to thread.
threadHdr->AddChild(reply, this, threadInThread);
return eSUCCESS;
}
// Get the header for the passed in message-id. Could return a DBMailMessageHdr
// because we do a deep find. Caller must RemoveReference DBMessageHdr when done with it.
DBMessageHdr *MessageDB::GetDBMessageHdrForID(const char * msgID)
{
DBMessageHdr *headerObject = NULL;
MSG_HeaderHandle headerHandle = MSG_DBHandle_GetHandleForMessageID(m_dbHandle, msgID);
if (headerHandle)
GetHeaderFromHandle(headerHandle, &headerObject);
return headerObject;
}
MessageKey MessageDB::GetMessageKeyForID(const char *msgID)
{
MessageKey retKey = kIdNone;
DBMessageHdr *msgHdr = GetDBMessageHdrForID(msgID);
if (msgHdr)
{
retKey = msgHdr->GetMessageKey();
delete msgHdr;
}
return retKey;
}
// caller needs to RemoveReference when finished.
DBThreadMessageHdr *MessageDB::GetDBThreadHdrForThreadID(MessageKey messageKey)
{
MSG_ThreadHandle threadHandle = MSG_DBHandle_GetThreadHeaderForThreadID(m_dbHandle, messageKey);
return GetThreadHeaderFromHandle(threadHandle);
}
DBThreadMessageHdr *MessageDB::GetDBThreadHdrForMsgHdr(DBMessageHdr *msgHdr)
{
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForThreadID(msgHdr->GetThreadId());
return threadHdr;
}
// Given the id of a message, find the thread header for the message's thread
// Returns NULL if we can't find the message hdr, or its thread.
// Caller needs to RemoveReference thread header
DBThreadMessageHdr *MessageDB::GetDBThreadHdrForMsgID(MessageKey messageKey)
{
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr == NULL)
return NULL;
DBThreadMessageHdr *threadHdr = GetDBThreadHdrForMsgHdr(msgHdr);
delete msgHdr;
return threadHdr;
}
// Given a MessageKey, return the threadId of its thread, or kIdNone
// if we can't find the given MessageKey.
MessageKey MessageDB::GetThreadIdForMsgId(MessageKey messageKey)
{
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr != NULL)
{
MessageKey threadId = msgHdr->GetThreadId();
delete msgHdr;
return threadId;
}
else
{
return kIdNone;
}
}
// caller needs to delete when finished.
DBMessageHdr *MessageDB::GetDBHdrForKey(MessageKey messageKey)
{
MSG_DBHandle dbHandle = MSG_DBHandle_GetHandleForKey(m_dbHandle, messageKey);
DBMessageHdr *headerObject = NULL;
if (dbHandle)
headerObject = new DBMessageHdr(dbHandle);
return headerObject;
}
// Test if the key we're about to add already exists (in which case
// the caller shouldn't add. This can happen in news for various reasons
// and should be handled).
XP_Bool MessageDB::KeyToAddExists(MessageKey messageKey)
{
// this relies on GetMessageHdr not touching msgHdr if it doesn't find key in db
DBMessageHdr *msgHdr = GetDBHdrForKey(messageKey);
if (msgHdr != NULL)
{
// this handles the bizarre case of the the db having all the
// headers but not having the highwater mark set, in which
// case it will always retrieve all headers.
if (m_dbFolderInfo != NULL)
m_dbFolderInfo->SetHighWater(messageKey);
delete msgHdr;
return TRUE;
}
return FALSE;
}
///////////////////// MessageHdrStruct methods
void MessageHdrStruct::SetSubject(const char * subject)
{
if (msg_StripRE(&subject, NULL))
{
m_flags |= kHasRe;
}
else
{
m_flags &= ~kHasRe;
}
XP_STRNCPY_SAFE(m_subject, subject, sizeof(m_subject));
}
void MessageHdrStruct::SetAuthor(const char * author)
{
XP_STRNCPY_SAFE(m_author, author, sizeof(m_author));
}
// Set message id, stripping off leading '<' and trailing '>', if any
void MessageHdrStruct::SetMessageID(const char * msgID)
{
if (msgID)
StripMessageId(msgID, m_messageId, sizeof(m_messageId));
}
/* static */void MessageHdrStruct::StripMessageId(const char *msgID, char *outMsgId, int msgIdLen)
{
if (*msgID == '<')
msgID++;
XP_STRNCPY_SAFE(outMsgId, msgID, msgIdLen);
char * lastChar = outMsgId + strlen(outMsgId) -1;
if (*lastChar == '>')
*lastChar = '\0';
}
void MessageHdrStruct::SetReferences(const char * referencesStr)
{
if (referencesStr)
XP_STRNCPY_SAFE(m_references, referencesStr, sizeof(m_references));
}
void MessageHdrStruct::SetDate(const char * date)
{
m_date = XP_ParseTimeString (date, FALSE);
}
void MessageHdrStruct::SetLines(uint32 lines)
{
m_messageSize = lines;
}
void MessageHdrStruct::SetSize(uint32 size)
{
m_messageSize = size;
}
// get the next <> delimited reference from nextRef and copy it into reference,
// which is a pointer to a buffer at least kMaxMsgIdLen long.
const char * MessageHdrStruct::GetReference(const char *nextRef, char *reference)
{
const char *ptr = nextRef;
while ((*ptr == '<' || *ptr == ' ') && *ptr)
ptr++;
for (int i = 0; *ptr && *ptr != '>' && i < kMaxMsgIdLen; i++)
*reference++ = *ptr++;
if (*ptr == '>')
ptr++;
*reference = '\0';
return ptr;
}
// Copy the corresponding fields from a full message header into a short message hdr.
void MessageDB::CopyFullHdrToShortHdr(MSG_MessageLine *msgHdr, MessageHdrStruct *fullHdr)
{
msgHdr->threadId = fullHdr->m_threadId;
msgHdr->messageKey = fullHdr->m_messageKey; //for threads, same as threadId
XP_STRNCPY_SAFE(msgHdr->subject, fullHdr->m_subject, sizeof(msgHdr->subject));
XP_STRNCPY_SAFE(msgHdr->author, fullHdr->m_author, sizeof(msgHdr->author));
msgHdr->date = fullHdr->m_date;
msgHdr->messageLines = fullHdr->m_messageSize; // lines for news articles,
// bytes for mail messages
// ###tw Is the above true
// yet?
msgHdr->priority = fullHdr->m_priority;
msgHdr->flags = fullHdr->m_flags;
msgHdr->level = fullHdr->m_level; // indentation level
msgHdr->numChildren = fullHdr->m_numChildren; // for top-level threads
msgHdr->numNewChildren = fullHdr->m_numNewChildren; // for top-level threads
}
// static helper functions to convert between kFlags and MSG_FLAG_*
void MessageDB::ConvertDBFlagsToPublicFlags(uint32 *flags)
{
uint32 publicFlags = 0;
publicFlags = (kSameAsMSG_FLAG & *flags);
if (*flags & kExpunged) // is this needed?
publicFlags |= MSG_FLAG_EXPUNGED;
if (*flags & kHasRe)
publicFlags |= MSG_FLAG_HAS_RE;
if (*flags & kIgnored)
publicFlags |= MSG_FLAG_IGNORED;
if (*flags & kPartial)
publicFlags |= MSG_FLAG_PARTIAL;
if (*flags & kMDNNeeded)
publicFlags |= MSG_FLAG_MDN_REPORT_NEEDED;
if (*flags & kMDNSent)
publicFlags |= MSG_FLAG_MDN_REPORT_SENT;
if (*flags & kTemplate)
publicFlags |= MSG_FLAG_TEMPLATE;
*flags = publicFlags;
}
void MessageDB::ConvertPublicFlagsToDBFlags(uint32 *flags)
{
uint32 dbFlags = 0;
dbFlags = (kSameAsMSG_FLAG & *flags);
if (*flags & MSG_FLAG_EXPUNGED) // is this needed?
dbFlags |= kExpunged ;
if (*flags & MSG_FLAG_HAS_RE)
dbFlags |= kHasRe ;
if (*flags & MSG_FLAG_IGNORED)
dbFlags |= kIgnored;
if (*flags & MSG_FLAG_PARTIAL)
dbFlags |= kPartial;
if (*flags & MSG_FLAG_MDN_REPORT_NEEDED)
dbFlags |= kMDNNeeded;
if (*flags & MSG_FLAG_MDN_REPORT_SENT)
dbFlags |= kMDNSent;
if (*flags & MSG_FLAG_TEMPLATE)
dbFlags |= kTemplate;
*flags = dbFlags;
}
ViewType MessageDB::GetViewType()
{
ViewType retViewType = ViewAllThreads;
if (m_dbFolderInfo)
{
retViewType = (ViewType) m_dbFolderInfo->GetViewType();
if (retViewType == ViewKilledThreads)
{
retViewType = ViewAllThreads;
m_dbFolderInfo->SetFlags(m_dbFolderInfo->GetFlags() | MSG_FOLDER_PREF_SHOWIGNORED);
}
}
return retViewType;
}
void MessageDB::SetViewType(ViewType viewType)
{
if (m_dbFolderInfo)
m_dbFolderInfo->SetViewType(viewType);
else
XP_ASSERT(FALSE);
}
MsgERR MessageDB::GetCachedPassword(XPStringObj &cachedPassword)
{
m_dbFolderInfo->GetCachedPassword(cachedPassword, m_dbHandle);
return eSUCCESS;
}
MsgERR MessageDB::SetCachedPassword(const char *password)
{
m_dbFolderInfo->SetCachedPassword(password, m_dbHandle);
return eSUCCESS;
}
XP_Bool MessageDB::HasCachedPassword()
{
XPStringObj password;
m_dbFolderInfo->GetCachedPassword(password, m_dbHandle);
return (XP_STRLEN(password) > 0);
}