mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 13:45:27 +00:00
2173 lines
57 KiB
C++
2173 lines
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);
|
|
}
|
|
|