You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5438 lines
147 KiB
5438 lines
147 KiB
//--------------------------------------------------------------------------
|
|
// MsgTable.cpp
|
|
//--------------------------------------------------------------------------
|
|
#include "pch.hxx"
|
|
#include "instance.h"
|
|
#include "msgtable.h"
|
|
#include "findfold.h"
|
|
#include "storutil.h"
|
|
#include "ruleutil.h"
|
|
#include "newsutil.h"
|
|
#include "xpcomm.h"
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CGROWTABLE
|
|
//--------------------------------------------------------------------------
|
|
#define CGROWTABLE 256
|
|
#define INVALID_ROWINDEX 0xffffffff
|
|
#define ROWSET_FETCH 100
|
|
|
|
//--------------------------------------------------------------------------
|
|
// GETTHREADSTATE
|
|
//--------------------------------------------------------------------------
|
|
typedef struct tagGETTHREADSTATE {
|
|
MESSAGEFLAGS dwFlags;
|
|
DWORD cHasFlags;
|
|
DWORD cChildren;
|
|
} GETTHREADSTATE, *LPGETTHREADSTATE;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// THREADISFROMME
|
|
//--------------------------------------------------------------------------
|
|
typedef struct tagTHREADISFROMME {
|
|
BOOL fResult;
|
|
LPROWINFO pRow;
|
|
} THREADISFROMME, *LPTHREADISFROMME;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// THREADHIDE
|
|
//--------------------------------------------------------------------------
|
|
typedef struct tagTHREADHIDE {
|
|
BOOL fNotify;
|
|
} THREADHIDE, *LPTHREADHIDE;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// GETSELECTIONSTATE
|
|
//--------------------------------------------------------------------------
|
|
typedef struct tagGETSELECTIONSTATE {
|
|
SELECTIONSTATE dwMask;
|
|
SELECTIONSTATE dwState;
|
|
} GETSELECTIONSTATE, *LPGETSELECTIONSTATE;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// GETTHREADPARENT
|
|
//--------------------------------------------------------------------------
|
|
typedef struct tagGETTHREADPARENT {
|
|
IDatabase *pDatabase;
|
|
IHashTable *pHash;
|
|
LPVOID pvResult;
|
|
} GETTHREADPARENT, *LPGETTHREADPARENT;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// IsInitialized
|
|
//--------------------------------------------------------------------------
|
|
#define IsInitialized(_pThis) \
|
|
(_pThis->m_pFolder && _pThis->m_pDB)
|
|
|
|
//--------------------------------------------------------------------------
|
|
// EnumRefsGetThreadParent
|
|
//--------------------------------------------------------------------------
|
|
HRESULT EnumRefsGetThreadParent(LPCSTR pszMessageId, DWORD_PTR dwCookie,
|
|
BOOL *pfDone)
|
|
{
|
|
// Locals
|
|
LPGETTHREADPARENT pGetParent = (LPGETTHREADPARENT)dwCookie;
|
|
|
|
// Trace
|
|
TraceCall("EnumRefsGetThreadParent");
|
|
|
|
// Find Message Id
|
|
if (SUCCEEDED(pGetParent->pHash->Find((LPSTR)pszMessageId, FALSE, &pGetParent->pvResult)))
|
|
{
|
|
// Ok
|
|
*pfDone = TRUE;
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::CMessageTable
|
|
//--------------------------------------------------------------------------
|
|
CMessageTable::CMessageTable(void)
|
|
{
|
|
TraceCall("CMessageTable::CMessageTable");
|
|
m_cRef = 1;
|
|
m_fSynching = FALSE;
|
|
m_pFolder = NULL;
|
|
m_pDB = NULL;
|
|
m_cRows = 0;
|
|
m_cView = 0;
|
|
m_cFiltered = 0;
|
|
m_cUnread = 0;
|
|
m_cAllocated = 0;
|
|
m_prgpRow = NULL;
|
|
m_prgpView = NULL;
|
|
m_pFindFolder = NULL;
|
|
m_pNotify = NULL;
|
|
m_fRelNotify = FALSE;
|
|
m_pThreadMsgId = NULL;
|
|
m_pThreadSubject = NULL;
|
|
m_pQuery = NULL;
|
|
m_cDelayed = 0;
|
|
m_fRegistered = FALSE;
|
|
m_clrWatched = 0;
|
|
m_pszEmail = NULL;
|
|
m_fLoaded = FALSE;
|
|
ZeroMemory(&m_SortInfo, sizeof(FOLDERSORTINFO));
|
|
ZeroMemory(&m_Notify, sizeof(NOTIFYQUEUE));
|
|
ZeroMemory(&m_Folder, sizeof(FOLDERINFO));
|
|
m_Notify.iRowMin = 0xffffffff;
|
|
m_Notify.fClean = TRUE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::~CMessageTable - Don't put any Asserts in this function
|
|
//--------------------------------------------------------------------------
|
|
CMessageTable::~CMessageTable()
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::~CMessageTable");
|
|
|
|
// Free Folder Info
|
|
g_pStore->FreeRecord(&m_Folder);
|
|
|
|
// Free Cached Rows
|
|
_FreeTable();
|
|
|
|
// Release the Folder
|
|
SafeRelease(m_pFolder);
|
|
|
|
// Release Query Object...
|
|
SafeRelease(m_pQuery);
|
|
|
|
// Release DB after folder, because releasing folder can cause call chain: ~CFolderSync->~CServerQ->
|
|
// CMessageList::OnComplete->CMessageTable::GetCount, for which we need a m_pDB.
|
|
if (m_pDB)
|
|
{
|
|
// Unregister
|
|
m_pDB->UnregisterNotify((IDatabaseNotify *)this);
|
|
|
|
// Release the Folder
|
|
m_pDB->Release();
|
|
|
|
// Null
|
|
m_pDB = NULL;
|
|
}
|
|
|
|
// Release the Find Folder
|
|
SafeRelease(m_pFindFolder);
|
|
|
|
// Set pCurrent
|
|
if (m_pNotify)
|
|
{
|
|
if (m_fRelNotify)
|
|
m_pNotify->Release();
|
|
m_pNotify = NULL;
|
|
}
|
|
|
|
// Free m_pszEmail
|
|
SafeMemFree(m_pszEmail);
|
|
|
|
// Free the Notification Queue
|
|
SafeMemFree(m_Notify.prgiRow);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::AddRef
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP_(ULONG) CMessageTable::AddRef(void)
|
|
{
|
|
TraceCall("CMessageTable::AddRef");
|
|
return InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Release
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP_(ULONG) CMessageTable::Release(void)
|
|
{
|
|
TraceCall("CMessageTable::Release");
|
|
LONG cRef = InterlockedDecrement(&m_cRef);
|
|
if (0 == cRef)
|
|
delete this;
|
|
return (ULONG)cRef;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::QueryInterface
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::QueryInterface(REFIID riid, LPVOID *ppv)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Stack
|
|
TraceCall("CMessageTable::QueryInterface");
|
|
|
|
// Invalid Arg
|
|
Assert(ppv);
|
|
|
|
// Find IID
|
|
if (IID_IUnknown == riid)
|
|
*ppv = (IUnknown *)(IMessageTable *)this;
|
|
else if (IID_IMessageTable == riid)
|
|
*ppv = (IMessageTable *)this;
|
|
else if (IID_IServiceProvider == riid)
|
|
*ppv = (IServiceProvider *)this;
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
hr = TraceResult(E_NOINTERFACE);
|
|
goto exit;
|
|
}
|
|
|
|
// AddRef It
|
|
((IUnknown *)*ppv)->AddRef();
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_FIsHidden
|
|
//--------------------------------------------------------------------------
|
|
BOOL CMessageTable::_FIsHidden(LPROWINFO pRow)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_FIsHidden");
|
|
|
|
// Hide Deleted ?
|
|
if (FALSE == m_SortInfo.fShowDeleted && ISFLAGSET(pRow->Message.dwFlags, ARF_ENDANGERED))
|
|
return(TRUE);
|
|
|
|
// Hide Offline Deleted ?
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_DELETED_OFFLINE))
|
|
return(TRUE);
|
|
|
|
// Not Hidden
|
|
return(FALSE);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_FIsFiltered
|
|
//--------------------------------------------------------------------------
|
|
BOOL CMessageTable::_FIsFiltered(LPROWINFO pRow)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_FIsFiltered");
|
|
|
|
// No Query Object
|
|
if (NULL == m_pQuery)
|
|
return(FALSE);
|
|
|
|
// No m_pQuery
|
|
return(S_OK == m_pQuery->Evaluate(&pRow->Message) ? FALSE : TRUE);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Initialize
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::Initialize(FOLDERID idFolder, IMessageServer *pServer,
|
|
BOOL fFindTable, IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::Initialize");
|
|
|
|
// Already Open ?
|
|
if (m_pFolder)
|
|
{
|
|
hr = TraceResult(E_UNEXPECTED);
|
|
goto exit;
|
|
}
|
|
|
|
// Search Folder ?
|
|
if (fFindTable)
|
|
{
|
|
// Create a Find Folder
|
|
IF_NULLEXIT(m_pFindFolder = new CFindFolder);
|
|
|
|
// Initialize
|
|
IF_FAILEXIT(hr = m_pFindFolder->Initialize(g_pStore, NULL, NOFLAGS, idFolder));
|
|
|
|
// Get an IMessageFolder
|
|
IF_FAILEXIT(hr = m_pFindFolder->QueryInterface(IID_IMessageFolder, (LPVOID *)&m_pFolder));
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Are there children
|
|
IF_FAILEXIT(hr = g_pStore->OpenFolder(idFolder, pServer, NOFLAGS, &m_pFolder));
|
|
}
|
|
|
|
// Get the folder id, it might have changed if this is a find folder
|
|
IF_FAILEXIT(hr = m_pFolder->GetFolderId(&idFolder));
|
|
|
|
// Get Folder Info
|
|
IF_FAILEXIT(hr = g_pStore->GetFolderInfo(idFolder, &m_Folder));
|
|
|
|
// Get the Database
|
|
IF_FAILEXIT(hr = m_pFolder->GetDatabase(&m_pDB));
|
|
|
|
// Set m_clrWatched
|
|
m_clrWatched = (WORD)DwGetOption(OPT_WATCHED_COLOR);
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::StartFind
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::StartFind(LPFINDINFO pCriteria, IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::StartFind");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this) || NULL == m_pFindFolder)
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize the Find Folder
|
|
IF_FAILEXIT(hr = m_pFindFolder->StartFind(pCriteria, pCallback));
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_GetSortChangeInfo
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_GetSortChangeInfo(LPFOLDERSORTINFO pSortInfo,
|
|
LPFOLDERUSERDATA pUserData, LPSORTCHANGEINFO pChange)
|
|
{
|
|
// Locals
|
|
HRESULT hr;
|
|
DWORD dwVersion;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_GetSortChangeInfo");
|
|
|
|
// INitialize
|
|
ZeroMemory(pChange, sizeof(SORTCHANGEINFO));
|
|
|
|
// Invalid ?
|
|
if (pSortInfo->ridFilter == RULEID_INVALID)
|
|
{
|
|
// Reset
|
|
pSortInfo->ridFilter = RULEID_VIEW_ALL;
|
|
}
|
|
|
|
// Get the filter version
|
|
hr = RuleUtil_HrGetFilterVersion(pSortInfo->ridFilter, &dwVersion);
|
|
|
|
// Bummer, that failed, so lets revert back to the default filter
|
|
if (FAILED(hr))
|
|
{
|
|
// View All filter
|
|
pSortInfo->ridFilter = RULEID_VIEW_ALL;
|
|
|
|
// Filter Changed...
|
|
pChange->fFilter = TRUE;
|
|
}
|
|
|
|
// Ohterwise, If this is a different filter
|
|
else if (pUserData->ridFilter != pSortInfo->ridFilter)
|
|
{
|
|
// Reset Version
|
|
pUserData->dwFilterVersion = dwVersion;
|
|
|
|
// Filter Changed...
|
|
pChange->fFilter = TRUE;
|
|
}
|
|
|
|
// Otherwise, did the version of this filter change
|
|
else if (pUserData->dwFilterVersion != dwVersion)
|
|
{
|
|
// Reset Version
|
|
pUserData->dwFilterVersion = dwVersion;
|
|
|
|
// Filter Changed...
|
|
pChange->fFilter = TRUE;
|
|
}
|
|
|
|
// Other filtering changes
|
|
if (pSortInfo->fShowDeleted != (BOOL)pUserData->fShowDeleted || pSortInfo->fShowReplies != (BOOL)pUserData->fShowReplies)
|
|
{
|
|
// Filter Changed...
|
|
pChange->fFilter = TRUE;
|
|
}
|
|
|
|
// Sort Order Change
|
|
if (pSortInfo->idColumn != (COLUMN_ID)pUserData->idSort || pSortInfo->fAscending != (BOOL)pUserData->fAscending)
|
|
{
|
|
// Sort Changed
|
|
pChange->fSort = TRUE;
|
|
}
|
|
|
|
// Thread Change...
|
|
if (pSortInfo->fThreaded != (BOOL)pUserData->fThreaded)
|
|
{
|
|
// Thread Change
|
|
pChange->fThread = TRUE;
|
|
}
|
|
|
|
// Expand Change
|
|
if (pSortInfo->fExpandAll != (BOOL)pUserData->fExpandAll)
|
|
{
|
|
// Expand Change
|
|
pChange->fExpand = TRUE;
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::OnSynchronizeComplete
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::OnSynchronizeComplete(void)
|
|
{
|
|
// Locals
|
|
DWORD i;
|
|
SORTCHANGEINFO Change={0};
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::OnSynchronizeComplete");
|
|
|
|
// If Not New...
|
|
if (FOLDER_NEWS != m_Folder.tyFolder)
|
|
goto exit;
|
|
|
|
// Finish any insert notifications
|
|
m_pDB->DispatchNotify(this);
|
|
|
|
// Nothing to do...
|
|
if (0 == m_cDelayed)
|
|
goto exit;
|
|
|
|
// Reset m_cDelayed
|
|
m_cDelayed = 0;
|
|
|
|
// ChangeSortOrThreading
|
|
_SortThreadFilterTable(&Change, m_SortInfo.fShowReplies);
|
|
|
|
// Remove fDelayed Bit...
|
|
for (i = 0; i < m_cRows; i++)
|
|
{
|
|
// Remove Delayed Bit
|
|
m_prgpRow[i]->fDelayed = FALSE;
|
|
}
|
|
|
|
exit:
|
|
// Reset m_fSynching
|
|
m_fSynching = FALSE;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::SetSortInfo
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::SetSortInfo(LPFOLDERSORTINFO pSortInfo,
|
|
IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HCURSOR hCursor=NULL;
|
|
HLOCK hLock=NULL;
|
|
FOLDERUSERDATA UserData;
|
|
SORTCHANGEINFO Change;
|
|
IF_DEBUG(DWORD dwTickStart=GetTickCount());
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::SetSortInfo");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Wait Cursor
|
|
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
// If this isn't a news folder then don't allow fShowReplies
|
|
if (FOLDER_NEWS != m_Folder.tyFolder)
|
|
{
|
|
// Clear fShowReplies
|
|
pSortInfo->fShowReplies = FALSE;
|
|
}
|
|
|
|
// Lock
|
|
IF_FAILEXIT(hr = m_pDB->Lock(&hLock));
|
|
|
|
// Get UserData
|
|
IF_FAILEXIT(hr = m_pDB->GetUserData(&UserData, sizeof(FOLDERUSERDATA)));
|
|
|
|
// Get Sort Change Information...
|
|
IF_FAILEXIT(hr = _GetSortChangeInfo(pSortInfo, &UserData, &Change));
|
|
|
|
// Save the SortInfo
|
|
CopyMemory(&m_SortInfo, pSortInfo, sizeof(FOLDERSORTINFO));
|
|
|
|
// Total Rebuild ?
|
|
if (NULL == m_prgpRow)
|
|
{
|
|
// Build RowTable
|
|
IF_FAILEXIT(hr = _BuildTable(pCallback));
|
|
}
|
|
|
|
// Sort or Threading Change Only
|
|
else if (Change.fSort || Change.fThread || Change.fFilter)
|
|
{
|
|
// ChangeSortOrThreading
|
|
_SortThreadFilterTable(&Change, Change.fFilter);
|
|
}
|
|
|
|
// Expand State Change
|
|
else if (Change.fExpand && m_SortInfo.fThreaded)
|
|
{
|
|
// Expand All ?
|
|
if (m_SortInfo.fExpandAll)
|
|
{
|
|
// Expand Everything
|
|
_ExpandThread(INVALID_ROWINDEX, FALSE, FALSE);
|
|
}
|
|
|
|
// Otherwise, collapse all
|
|
else
|
|
{
|
|
// Collapse Everything
|
|
_CollapseThread(INVALID_ROWINDEX, FALSE);
|
|
}
|
|
}
|
|
|
|
// Otherwise, just refresh the filter
|
|
else
|
|
{
|
|
// RefreshFilter
|
|
_RefreshFilter();
|
|
}
|
|
|
|
// Save Sort Order
|
|
UserData.fAscending = pSortInfo->fAscending;
|
|
UserData.idSort = pSortInfo->idColumn;
|
|
UserData.fThreaded = pSortInfo->fThreaded;
|
|
UserData.ridFilter = pSortInfo->ridFilter;
|
|
UserData.fExpandAll = pSortInfo->fExpandAll;
|
|
UserData.fShowDeleted = (BYTE) !!(pSortInfo->fShowDeleted);
|
|
UserData.fShowReplies = (BYTE) !!(pSortInfo->fShowReplies);
|
|
|
|
// Get UserData
|
|
IF_FAILEXIT(hr = m_pDB->SetUserData(&UserData, sizeof(FOLDERUSERDATA)));
|
|
|
|
// Have I Registered For Notifications Yet ?
|
|
if (FALSE == m_fRegistered)
|
|
{
|
|
// Register for Notifications
|
|
IF_FAILEXIT(hr = m_pDB->RegisterNotify(IINDEX_PRIMARY, REGISTER_NOTIFY_NOADDREF, 0, (IDatabaseNotify *)this));
|
|
|
|
// Registered
|
|
m_fRegistered = TRUE;
|
|
}
|
|
|
|
exit:
|
|
// Unlock
|
|
m_pDB->Unlock(&hLock);
|
|
|
|
// Reset Cursor
|
|
SetCursor(hCursor);
|
|
|
|
// Time to Sort
|
|
TraceInfo(_MSG("Table Sort Time: %d Milli-Seconds", GetTickCount() - dwTickStart));
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetSortInfo
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetSortInfo(LPFOLDERSORTINFO pSortInfo)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
FOLDERUSERDATA UserData;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetSortInfo");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
ZeroMemory(pSortInfo, sizeof(FOLDERSORTINFO));
|
|
|
|
// Get Sort Information
|
|
IF_FAILEXIT(hr = m_pDB->GetUserData(&UserData, sizeof(FOLDERUSERDATA)));
|
|
|
|
// Save Sort Order if not threaded
|
|
pSortInfo->fAscending = UserData.fAscending;
|
|
|
|
// Threaded
|
|
pSortInfo->fThreaded = UserData.fThreaded;
|
|
|
|
// Save Sort Column
|
|
pSortInfo->idColumn = (COLUMN_ID)UserData.idSort;
|
|
|
|
// Expand All
|
|
pSortInfo->fExpandAll = UserData.fExpandAll;
|
|
|
|
// Set rid Filter
|
|
pSortInfo->ridFilter = UserData.ridFilter;
|
|
|
|
// Set deleted state
|
|
pSortInfo->fShowDeleted = UserData.fShowDeleted;
|
|
|
|
// Set replies
|
|
pSortInfo->fShowReplies = UserData.fShowReplies;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_GetRowFromIndex
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_GetRowFromIndex(ROWINDEX iRow, LPROWINFO *ppRow)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_GetRowFromIndex");
|
|
|
|
// Out of View Range ?
|
|
if (iRow >= m_cView)
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
// Bad Row Index
|
|
if (NULL == m_prgpView[iRow])
|
|
{
|
|
hr = TraceResult(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Set pRow
|
|
pRow = m_prgpView[iRow];
|
|
|
|
// Validate Reserved...
|
|
IxpAssert(pRow->Message.dwReserved == (DWORD_PTR)pRow);
|
|
|
|
// Must have pAllocated
|
|
IxpAssert(pRow->Message.pAllocated);
|
|
|
|
// Must have References
|
|
IxpAssert(pRow->cRefs > 0);
|
|
|
|
// Set pprow
|
|
*ppRow = pRow;
|
|
|
|
exit:
|
|
// Return the Row
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_CreateRow
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_CreateRow(LPMESSAGEINFO pMessage, LPROWINFO *ppRow)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_CreateRow");
|
|
|
|
// Allocate the Row
|
|
IF_FAILEXIT(hr = m_pDB->HeapAllocate(HEAP_ZERO_MEMORY, sizeof(ROWINFO), (LPVOID *)&pRow));
|
|
|
|
// Save the Highlight
|
|
pRow->wHighlight = pMessage->wHighlight;
|
|
|
|
// Copy the message
|
|
CopyMemory(&pRow->Message, pMessage, sizeof(MESSAGEINFO));
|
|
|
|
// Set pRow into
|
|
pRow->Message.dwReserved = (DWORD_PTR)pRow;
|
|
|
|
// OneRef
|
|
pRow->cRefs = 1;
|
|
|
|
// Return the Row
|
|
*ppRow = pRow;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_DeleteRowFromThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_DeleteRowFromThread(LPROWINFO pRow, BOOL fNotify)
|
|
{
|
|
// Locals
|
|
LPROWINFO pCurrent;
|
|
LPROWINFO pNewRow;
|
|
ROWINDEX iMin;
|
|
ROWINDEX iMax;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_DeleteRowFromThread");
|
|
|
|
// Abort
|
|
if (FALSE == m_SortInfo.fThreaded || pRow->fFiltered || pRow->fHidden)
|
|
return(S_OK);
|
|
|
|
// Notify
|
|
if (fNotify)
|
|
{
|
|
// _RefreshThread
|
|
_GetThreadIndexRange(pRow, TRUE, &iMin, &iMax);
|
|
}
|
|
|
|
// If there is a messageid
|
|
if (pRow->Message.pszMessageId)
|
|
{
|
|
// Remove pRow from both threading indexes!!!
|
|
if (SUCCEEDED(m_pThreadMsgId->Find(pRow->Message.pszMessageId, TRUE, (LPVOID *)&pCurrent)))
|
|
{
|
|
// If this isn't this row, then put it back...
|
|
if (pRow != pCurrent)
|
|
{
|
|
// Put It Back
|
|
m_pThreadMsgId->Insert(pRow->Message.pszMessageId, (LPVOID)pCurrent, HF_NO_DUPLICATES);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there is a normalized subject and a subject hash table
|
|
if (NULL == pRow->pParent && pRow->Message.pszNormalSubj && m_pThreadSubject)
|
|
{
|
|
// Remove pRow from both threading indexes!!!
|
|
if (SUCCEEDED(m_pThreadSubject->Find(pRow->Message.pszNormalSubj, TRUE, (LPVOID *)&pCurrent)))
|
|
{
|
|
// If this isn't this row, then put it back...
|
|
if (pRow != pCurrent)
|
|
{
|
|
// Put It Back
|
|
m_pThreadSubject->Insert(pRow->Message.pszNormalSubj, (LPVOID)pCurrent, HF_NO_DUPLICATES);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we have a Child
|
|
if (pRow->pChild)
|
|
{
|
|
// Set pNewRow
|
|
pNewRow = pRow->pChild;
|
|
|
|
// Promote Children of pNewRow to be at the same level as the children of pRow
|
|
if (pNewRow->pChild)
|
|
{
|
|
// Walk until I find the last sibling
|
|
pCurrent = pNewRow->pChild;
|
|
|
|
// Continue
|
|
while (pCurrent->pSibling)
|
|
{
|
|
// Validate Parent
|
|
Assert(pCurrent->pParent == pNewRow);
|
|
|
|
// Goto Next
|
|
pCurrent = pCurrent->pSibling;
|
|
}
|
|
|
|
// Make pLastSibling->pSibling
|
|
pCurrent->pSibling = pNewRow->pSibling;
|
|
}
|
|
|
|
// Otherwise, Child is the first sibling of pNewRow
|
|
else
|
|
{
|
|
// Set First Child
|
|
pNewRow->pChild = pNewRow->pSibling;
|
|
}
|
|
|
|
// Fixup other children of pRow to have a new parent of pNewRow...
|
|
pCurrent = pNewRow->pSibling;
|
|
|
|
// While we have siblings...
|
|
while (pCurrent)
|
|
{
|
|
// Current Parent is pRow
|
|
Assert(pRow == pCurrent->pParent);
|
|
|
|
// Reset the parent...
|
|
pCurrent->pParent = pNewRow;
|
|
|
|
// Goto Next Sibling
|
|
pCurrent = pCurrent->pSibling;
|
|
}
|
|
|
|
// Set the Sibling of pNewRow to be the same sibling as pRow
|
|
pNewRow->pSibling = pRow->pSibling;
|
|
|
|
// Reset Parent of pNewRow
|
|
pNewRow->pParent = pRow->pParent;
|
|
|
|
// Assume Expanded Flags...
|
|
pNewRow->fExpanded = pRow->fExpanded;
|
|
|
|
// Clear dwState
|
|
pNewRow->dwState = 0;
|
|
|
|
// If pNewRow is now a Root.. Need to adjust the subject hash table..
|
|
if (NULL == pNewRow->pParent && pNewRow->Message.pszNormalSubj && m_pThreadSubject)
|
|
{
|
|
// Remove pRow from both threading indexes!!!
|
|
m_pThreadSubject->Insert(pNewRow->Message.pszNormalSubj, (LPVOID)pNewRow, HF_NO_DUPLICATES);
|
|
}
|
|
}
|
|
|
|
// Otherwise...
|
|
else
|
|
{
|
|
// Set pNewRow for doing sibling/parent fixup
|
|
pNewRow = pRow->pSibling;
|
|
}
|
|
|
|
// Otherwise, if there is a parent...
|
|
if (pRow->pParent)
|
|
{
|
|
// Parent must have children
|
|
Assert(pRow->pParent->pChild);
|
|
|
|
// First Child of pRow->pParent
|
|
if (pRow == pRow->pParent->pChild)
|
|
{
|
|
// Set new first child to pRow's Sibling
|
|
pRow->pParent->pChild = pNewRow;
|
|
}
|
|
|
|
// Otherwise, Walk pParent's Child and remove pRow from Sibling List
|
|
else
|
|
{
|
|
// Set pPrevious
|
|
LPROWINFO pPrevious=NULL;
|
|
|
|
// Set pCurrent
|
|
pCurrent = pRow->pParent->pChild;
|
|
|
|
// Loop
|
|
while (pCurrent)
|
|
{
|
|
// Is this the row to remove!
|
|
if (pRow == pCurrent)
|
|
{
|
|
// Better be a previous
|
|
Assert(pPrevious);
|
|
|
|
// pPrevious's Sibling better be pRow
|
|
Assert(pPrevious->pSibling == pRow);
|
|
|
|
// Set New Sibling
|
|
pPrevious->pSibling = pNewRow;
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
|
|
// Set pPrevious
|
|
pPrevious = pCurrent;
|
|
|
|
// Set pCurrent
|
|
pCurrent = pCurrent->pSibling;
|
|
}
|
|
|
|
// Validate
|
|
Assert(pRow == pCurrent);
|
|
}
|
|
|
|
// Set row state
|
|
pRow->pParent->dwState = 0;
|
|
}
|
|
|
|
// UpdateRows
|
|
if (fNotify && INVALID_ROWINDEX != iMin && INVALID_ROWINDEX != iMax)
|
|
{
|
|
// Queue the Notification
|
|
_QueueNotification(TRANSACTION_UPDATE, iMin, iMax);
|
|
}
|
|
|
|
// Clear the row
|
|
pRow->pParent = pRow->pChild = pRow->pSibling = NULL;
|
|
|
|
// done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_PGetThreadRoot
|
|
//--------------------------------------------------------------------------
|
|
LPROWINFO CMessageTable::_PGetThreadRoot(LPROWINFO pRow)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_PGetThreadRoot");
|
|
|
|
// Validate
|
|
Assert(pRow);
|
|
|
|
// Set Root
|
|
LPROWINFO pRoot = pRow;
|
|
|
|
// While there is a parent
|
|
while (pRoot->pParent)
|
|
{
|
|
// Go Up One
|
|
pRoot = pRoot->pParent;
|
|
}
|
|
|
|
// Done
|
|
return(pRoot);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_GetThreadIndexRange
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_GetThreadIndexRange(LPROWINFO pRow, BOOL fClearState,
|
|
LPROWINDEX piMin, LPROWINDEX piMax)
|
|
{
|
|
// Locals
|
|
LPROWINFO pRoot;
|
|
ROWINDEX iRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_GetThreadIndexRange");
|
|
|
|
// Validate Args
|
|
Assert(pRow && piMin && piMax);
|
|
|
|
// Initialize
|
|
*piMin = *piMax = INVALID_ROWINDEX;
|
|
|
|
// Get the Root
|
|
pRoot = _PGetThreadRoot(pRow);
|
|
|
|
// If the root isn't visible, then don't bother...
|
|
if (FALSE == pRoot->fVisible)
|
|
return(S_OK);
|
|
|
|
// The Root Must be Visible, not filtered and not hidden
|
|
Assert(FALSE == pRoot->fFiltered && FALSE == pRoot->fHidden);
|
|
|
|
// Get the Row Index
|
|
SideAssert(SUCCEEDED(GetRowIndex(pRoot->Message.idMessage, piMin)));
|
|
|
|
// Init piMax
|
|
(*piMax) = (*piMin);
|
|
|
|
// Loop until I hit the next row in the view who is the root
|
|
while (1)
|
|
{
|
|
// Set irow
|
|
iRow = (*piMax) + 1;
|
|
|
|
// Dont
|
|
if (iRow >= m_cView)
|
|
break;
|
|
|
|
// Look at the Next Row
|
|
if (NULL == m_prgpView[iRow]->pParent)
|
|
break;
|
|
|
|
// Increment piMax
|
|
(*piMax) = iRow;
|
|
}
|
|
|
|
// ClearState
|
|
if (fClearState)
|
|
{
|
|
// If Clear State
|
|
_WalkMessageThread(pRoot, WALK_THREAD_CURRENT, NULL, _WalkThreadClearState);
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_LinkRowIntoThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_LinkRowIntoThread(LPROWINFO pParent, LPROWINFO pRow,
|
|
BOOL fNotify)
|
|
{
|
|
// Locals
|
|
BOOL fHadChildren=(pParent->pChild ? TRUE : FALSE);
|
|
LPROWINFO pCurrent;
|
|
LPROWINFO pPrevious=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_LinkRowIntoThread");
|
|
|
|
// Set Parent
|
|
pRow->pParent = pParent;
|
|
|
|
// Loop through the children and find the right place to insert this child
|
|
pCurrent = pParent->pChild;
|
|
|
|
// Loop
|
|
while (pCurrent)
|
|
{
|
|
// Compare Received Time...
|
|
if (CompareFileTime(&pRow->Message.ftReceived, &pCurrent->Message.ftReceived) <= 0)
|
|
break;
|
|
|
|
// Set Previous
|
|
pPrevious = pCurrent;
|
|
|
|
// Goto Next
|
|
pCurrent = pCurrent->pSibling;
|
|
}
|
|
|
|
// If there is a pPrevious
|
|
if (pPrevious)
|
|
{
|
|
// Set Sibling of pRow
|
|
pRow->pSibling = pPrevious->pSibling;
|
|
|
|
// Point pPrevious to pRow
|
|
pPrevious->pSibling = pRow;
|
|
}
|
|
|
|
// Otherwise, set parent child
|
|
else
|
|
{
|
|
// Set Sibling of pRow
|
|
pRow->pSibling = pParent->pChild;
|
|
|
|
// First Row ?
|
|
if (NULL == pParent->pChild && FALSE == m_fLoaded)
|
|
{
|
|
// Set Expanded
|
|
pParent->fExpanded = m_SortInfo.fExpandAll;
|
|
}
|
|
|
|
// Set Parent Child
|
|
pParent->pChild = pRow;
|
|
}
|
|
|
|
// Not Loaded
|
|
if (FALSE == m_fLoaded || TRUE == pRow->fDelayed)
|
|
{
|
|
// Set Expanded Bit on this row...
|
|
pRow->fExpanded = pParent->fExpanded;
|
|
}
|
|
|
|
// If this is the first child and we have expand all on
|
|
if (fNotify)
|
|
{
|
|
// First Child...
|
|
if (pParent->fVisible && (m_SortInfo.fExpandAll || pParent->fExpanded))
|
|
{
|
|
// Locals
|
|
ROWINDEX iParent;
|
|
|
|
// Expand this thread...
|
|
SideAssert(SUCCEEDED(GetRowIndex(pParent->Message.idMessage, &iParent)));
|
|
|
|
// Expand...
|
|
_ExpandThread(iParent, TRUE, FALSE);
|
|
}
|
|
|
|
// Otherwise, update this thread range...
|
|
else if (m_pNotify)
|
|
{
|
|
// Locals
|
|
ROWINDEX iMin;
|
|
ROWINDEX iMax;
|
|
|
|
// _RefreshThread
|
|
_GetThreadIndexRange(pParent, TRUE, &iMin, &iMax);
|
|
|
|
// UpdateRows
|
|
if (INVALID_ROWINDEX != iMin && INVALID_ROWINDEX != iMax)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iMin, iMax);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_FindThreadParentByRef
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_FindThreadParentByRef(LPCSTR pszReferences,
|
|
LPROWINFO *ppParent)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
GETTHREADPARENT GetParent;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_FindThreadParentByRef");
|
|
|
|
// Init
|
|
*ppParent = NULL;
|
|
|
|
// Setup GetParent
|
|
GetParent.pDatabase = m_pDB;
|
|
GetParent.pHash = m_pThreadMsgId;
|
|
GetParent.pvResult = NULL;
|
|
|
|
// EnumerateReferences
|
|
IF_FAILEXIT(hr = EnumerateRefs(pszReferences, (DWORD_PTR)&GetParent, EnumRefsGetThreadParent));
|
|
|
|
// Not Found
|
|
if (NULL == GetParent.pvResult)
|
|
{
|
|
hr = S_FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
// Return the Row
|
|
*ppParent = (LPROWINFO)GetParent.pvResult;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_InsertRowIntoThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_InsertRowIntoThread(LPROWINFO pRow, BOOL fNotify)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pParent;
|
|
LPMESSAGEINFO pMessage=&pRow->Message;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_InsertRowIntoThread");
|
|
|
|
// Better not be hidden or filtered
|
|
Assert(FALSE == pRow->fFiltered && FALSE == pRow->fHidden);
|
|
|
|
// Find Parent by References Line
|
|
if (S_OK == _FindThreadParentByRef(pMessage->pszReferences, &pParent))
|
|
{
|
|
// Link row into thread
|
|
_LinkRowIntoThread(pParent, pRow, fNotify);
|
|
|
|
// Ok
|
|
hr = S_OK;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Subject Threading
|
|
if (m_pThreadSubject)
|
|
{
|
|
// If there is a subject
|
|
if (NULL == pRow->Message.pszNormalSubj)
|
|
{
|
|
hr = S_FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
// Try to find a message who has the same normalized subject....
|
|
if (SUCCEEDED(m_pThreadSubject->Find(pRow->Message.pszNormalSubj, FALSE, (LPVOID *)&pParent)))
|
|
{
|
|
// Should we Swap the parent and pRow ?
|
|
if (CompareFileTime(&pRow->Message.ftReceived, &pParent->Message.ftReceived) <= 0)
|
|
{
|
|
// Locals
|
|
ROWINDEX iRow;
|
|
|
|
// Make pRow be the Root
|
|
IxpAssert(NULL == pParent->pParent && NULL == pParent->pSibling && pParent->fVisible);
|
|
|
|
// No Parent for pRow
|
|
pRow->pParent = NULL;
|
|
|
|
// Set Expanded
|
|
pRow->fExpanded = pParent->fExpanded;
|
|
|
|
// Get the Row Index
|
|
SideAssert(SUCCEEDED(GetRowIndex(pParent->Message.idMessage, &iRow)));
|
|
|
|
// Validate
|
|
Assert(m_prgpView[iRow] == pParent);
|
|
|
|
// Replace with pRow
|
|
m_prgpView[iRow] = pRow;
|
|
|
|
// Visible
|
|
pRow->fVisible = TRUE;
|
|
|
|
// Clear Visible...
|
|
pParent->fVisible = FALSE;
|
|
|
|
// Replace the Subject Token
|
|
SideAssert(SUCCEEDED(m_pThreadSubject->Replace(pRow->Message.pszNormalSubj, (LPVOID *)pRow)));
|
|
|
|
// Link row into thread
|
|
_LinkRowIntoThread(pRow, pParent, fNotify);
|
|
}
|
|
|
|
// Otherwise..
|
|
else
|
|
{
|
|
// Link row into thread
|
|
_LinkRowIntoThread(pParent, pRow, fNotify);
|
|
}
|
|
|
|
// Success
|
|
hr = S_OK;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Not Found
|
|
hr = S_FALSE;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_RefreshFilter
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_RefreshFilter(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD i;
|
|
LPROWINFO pRow;
|
|
SORTCHANGEINFO Change={0};
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_RefreshFilter");
|
|
|
|
// No filter currently enabled
|
|
if (NULL == m_pQuery)
|
|
goto exit;
|
|
|
|
// Loop through current rows...
|
|
for (i = 0; i < m_cRows; i++)
|
|
{
|
|
// Set pRow
|
|
pRow = m_prgpRow[i];
|
|
|
|
// If Not Hidden and Not Filtered
|
|
if (pRow->fFiltered)
|
|
continue;
|
|
|
|
// Set Filtered Bit
|
|
if (FALSE == _FIsFiltered(pRow))
|
|
continue;
|
|
|
|
// Adjust m_cUnread
|
|
_AdjustUnreadCount(pRow, -1);
|
|
|
|
// Hide the Row
|
|
_HideRow(pRow, FALSE);
|
|
|
|
// Filtered
|
|
pRow->fFiltered = TRUE;
|
|
|
|
// Increment m_cFiltered
|
|
m_cFiltered++;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_SortThreadFilterTable
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_SortThreadFilterTable(LPSORTCHANGEINFO pChange,
|
|
BOOL fApplyFilter)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD i;
|
|
LPROWINFO pRow;
|
|
QUERYINFO Query={0};
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_SortThreadFilterTable");
|
|
|
|
// Nothing to resort ?
|
|
if (0 == m_cRows)
|
|
goto exit;
|
|
|
|
// Nuke the View Index
|
|
m_cView = 0;
|
|
|
|
// Do the Filter
|
|
if (pChange->fFilter)
|
|
{
|
|
// Get m_pQuery
|
|
SafeRelease(m_pQuery);
|
|
|
|
// Build a Query Object
|
|
if (SUCCEEDED(RuleUtil_HrBuildQuerysFromFilter(m_SortInfo.ridFilter, &Query)) && Query.pszQuery)
|
|
{
|
|
// Get the Query Object
|
|
IF_FAILEXIT(hr = g_pDBSession->OpenQuery(m_pDB, Query.pszQuery, &m_pQuery));
|
|
}
|
|
}
|
|
|
|
// Drop the Threading Indexes
|
|
SafeRelease(m_pThreadMsgId);
|
|
SafeRelease(m_pThreadSubject);
|
|
|
|
// If Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Create a New Hash TAble
|
|
IF_FAILEXIT(hr = MimeOleCreateHashTable(max(1024, m_cRows), FALSE, &m_pThreadMsgId));
|
|
|
|
// Don't do Subject threading?
|
|
if (DwGetOption(OPT_SUBJECT_THREADING) || (FOLDER_NEWS != m_Folder.tyFolder))
|
|
{
|
|
// Create a Subject Hash Table
|
|
IF_FAILEXIT(hr = MimeOleCreateHashTable(max(1024, m_cRows), FALSE, &m_pThreadSubject));
|
|
}
|
|
}
|
|
|
|
// Reset Unread and Filtered
|
|
m_cUnread = m_cFiltered = 0;
|
|
|
|
// Loop through current rows...
|
|
for (i = 0; i < m_cRows; i++)
|
|
{
|
|
// Set pRow
|
|
pRow = m_prgpRow[i];
|
|
|
|
// Reset Visible
|
|
pRow->fVisible = FALSE;
|
|
|
|
// Clear Threading
|
|
pRow->pParent = pRow->pChild = pRow->pSibling = NULL;
|
|
|
|
// Clear dwState
|
|
pRow->dwState = 0;
|
|
|
|
// If Threaded..
|
|
if (FALSE == m_SortInfo.fThreaded)
|
|
{
|
|
// Clear Expanded
|
|
pRow->fExpanded = FALSE;
|
|
}
|
|
|
|
// Otherwise, if the row is hidden
|
|
else if (pRow->fFiltered || pRow->fHidden)
|
|
{
|
|
// Reset Expanded State
|
|
pRow->fExpanded = m_SortInfo.fExpandAll;
|
|
}
|
|
|
|
// Do filter
|
|
if (fApplyFilter)
|
|
{
|
|
// Reset the Highlight
|
|
pRow->Message.wHighlight = pRow->wHighlight;
|
|
|
|
// If not doing show repiles
|
|
if (FALSE == m_SortInfo.fShowReplies)
|
|
{
|
|
// Set Filtered Bit
|
|
pRow->fFiltered = _FIsFiltered(pRow);
|
|
|
|
// Set Hidden Bit
|
|
pRow->fHidden = _FIsHidden(pRow);
|
|
}
|
|
|
|
// Otherwise, clear the filtered bits
|
|
else
|
|
{
|
|
// Clear the Bits
|
|
pRow->fFiltered = pRow->fHidden = FALSE;
|
|
}
|
|
}
|
|
|
|
// If Not Filtered
|
|
if (FALSE == pRow->fFiltered && FALSE == pRow->fHidden)
|
|
{
|
|
// Hash the MessageId
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Insert Message Id into the hash table
|
|
if (pRow->Message.pszMessageId)
|
|
{
|
|
// Insert It
|
|
m_pThreadMsgId->Insert(pRow->Message.pszMessageId, (LPVOID)pRow, HF_NO_DUPLICATES);
|
|
}
|
|
}
|
|
|
|
// Otherwise, add entry to view index
|
|
else
|
|
{
|
|
// Visible
|
|
pRow->fVisible = TRUE;
|
|
|
|
// Put into m_prgpView
|
|
m_prgpView[m_cView] = pRow;
|
|
|
|
// Increment View Count
|
|
m_cView++;
|
|
}
|
|
|
|
// Adjust m_cUnread
|
|
_AdjustUnreadCount(pRow, 1);
|
|
}
|
|
|
|
// Otherwise, free the record
|
|
else
|
|
{
|
|
// Count Filtered
|
|
m_cFiltered++;
|
|
}
|
|
}
|
|
|
|
// Sort the Table
|
|
_SortAndThreadTable(fApplyFilter);
|
|
|
|
// If Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// If the Filter Changed, then re-apply collapse and expand...
|
|
if (pChange->fThread)
|
|
{
|
|
// Expand All ?
|
|
if (m_SortInfo.fExpandAll)
|
|
{
|
|
// Expand Everything
|
|
_ExpandThread(INVALID_ROWINDEX, FALSE, FALSE);
|
|
}
|
|
|
|
// Otherwise, collapse all
|
|
else
|
|
{
|
|
// Collapse Everything
|
|
_CollapseThread(INVALID_ROWINDEX, FALSE);
|
|
}
|
|
}
|
|
|
|
// Otherwise, re-expand threads that were expanded and expand newly deferred inserted rows
|
|
else
|
|
{
|
|
// Re-Expand Threads that were expanded...
|
|
_ExpandThread(INVALID_ROWINDEX, FALSE, TRUE);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(Query.pszQuery);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_BuildTable
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_BuildTable(IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
QUERYINFO Query={0};
|
|
DWORD cRecords;
|
|
DWORD cFetched;
|
|
DWORD i;
|
|
DWORD cMessages=0;
|
|
DWORD cUnread=0;
|
|
DWORD cWatched=0;
|
|
DWORD cWatchedUnread=0;
|
|
HROWSET hRowset=NULL;
|
|
LPMESSAGEINFO pMessage;
|
|
MESSAGEINFO rgMessage[ROWSET_FETCH];
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_BuildTable");
|
|
|
|
// Free my current row table
|
|
_FreeTable();
|
|
|
|
// Get m_pQuery
|
|
SafeRelease(m_pQuery);
|
|
|
|
// Build a Query Object
|
|
if (SUCCEEDED(RuleUtil_HrBuildQuerysFromFilter(m_SortInfo.ridFilter, &Query)) && Query.pszQuery)
|
|
{
|
|
// Get the Query Object
|
|
IF_FAILEXIT(hr = g_pDBSession->OpenQuery(m_pDB, Query.pszQuery, &m_pQuery));
|
|
}
|
|
|
|
// Get the Row Count
|
|
IF_FAILEXIT(hr = m_pDB->GetRecordCount(IINDEX_PRIMARY, &cRecords));
|
|
|
|
// Do OnBegin
|
|
if (pCallback)
|
|
pCallback->OnBegin(SOT_SORTING, NULL, (IOperationCancel *)this);
|
|
|
|
// If Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Create a New Hash TAble
|
|
IF_FAILEXIT(hr = MimeOleCreateHashTable(max(1024, cRecords), FALSE, &m_pThreadMsgId));
|
|
|
|
// Don't do Subject threading?
|
|
if (DwGetOption(OPT_SUBJECT_THREADING) || (FOLDER_NEWS != m_Folder.tyFolder))
|
|
{
|
|
// Create a Subject Hash Table
|
|
IF_FAILEXIT(hr = MimeOleCreateHashTable(max(1024, cRecords), FALSE, &m_pThreadSubject));
|
|
}
|
|
}
|
|
|
|
// Allocate the Row Table
|
|
IF_FAILEXIT(hr = HrAlloc((LPVOID *)&m_prgpRow, sizeof(LPROWINFO) * (cRecords + CGROWTABLE)));
|
|
|
|
// Allocate the View Table
|
|
IF_FAILEXIT(hr = HrAlloc((LPVOID *)&m_prgpView, sizeof(LPROWINFO) * (cRecords + CGROWTABLE)));
|
|
|
|
// Set m_cAllocated
|
|
m_cAllocated = cRecords + CGROWTABLE;
|
|
|
|
// Create a Rowset
|
|
IF_FAILEXIT(hr = m_pDB->CreateRowset(IINDEX_PRIMARY, 0, &hRowset));
|
|
|
|
// Walk the Rowset
|
|
while (S_OK == m_pDB->QueryRowset(hRowset, ROWSET_FETCH, (LPVOID *)rgMessage, &cFetched))
|
|
{
|
|
// Loop through the Rows
|
|
for (i=0; i<cFetched; i++)
|
|
{
|
|
// Set pMessage
|
|
pMessage = &rgMessage[i];
|
|
|
|
// Count Messages
|
|
cMessages++;
|
|
|
|
// Create a Row
|
|
IF_FAILEXIT(hr = _CreateRow(pMessage, &pRow));
|
|
|
|
// Unread ?
|
|
if (!ISFLAGSET(pRow->Message.dwFlags, ARF_READ))
|
|
{
|
|
// Increment cUnread
|
|
cUnread++;
|
|
|
|
// Watched
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_WATCH))
|
|
cWatchedUnread++;
|
|
}
|
|
|
|
// Watched
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_WATCH))
|
|
cWatched++;
|
|
|
|
// If not showing repiles
|
|
if (FALSE == m_SortInfo.fShowReplies)
|
|
{
|
|
// Set Filtered Bit
|
|
pRow->fFiltered = _FIsFiltered(pRow);
|
|
|
|
// Set Hidden Bit
|
|
pRow->fHidden = _FIsHidden(pRow);
|
|
}
|
|
|
|
// If Not Filtered
|
|
if (FALSE == pRow->fFiltered && FALSE == pRow->fHidden)
|
|
{
|
|
// Hash the MessageId
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Insert Message Id into the hash table
|
|
if (pRow->Message.pszMessageId)
|
|
{
|
|
// Insert It
|
|
m_pThreadMsgId->Insert(pRow->Message.pszMessageId, (LPVOID)pRow, HF_NO_DUPLICATES);
|
|
}
|
|
}
|
|
|
|
// Otherwise, add entry to view index
|
|
else
|
|
{
|
|
// Visible
|
|
pRow->fVisible = TRUE;
|
|
|
|
// Put into m_prgpView
|
|
m_prgpView[m_cView] = pRow;
|
|
|
|
// Increment View Count
|
|
m_cView++;
|
|
}
|
|
|
|
// Adjust m_cUnread
|
|
_AdjustUnreadCount(pRow, 1);
|
|
}
|
|
|
|
// Otherwise, free the record
|
|
else
|
|
{
|
|
// Count Filtered
|
|
m_cFiltered++;
|
|
}
|
|
|
|
// Store the Row
|
|
m_prgpRow[m_cRows] = pRow;
|
|
|
|
// Increment Row Count
|
|
m_cRows++;
|
|
}
|
|
|
|
// Do OnBegin
|
|
if (pCallback)
|
|
pCallback->OnProgress(SOT_SORTING, m_cRows, cRecords, NULL);
|
|
}
|
|
|
|
// Reset the folder count
|
|
m_pFolder->ResetFolderCounts(cMessages, cUnread, cWatchedUnread, cWatched);
|
|
|
|
// Sort the Table
|
|
_SortAndThreadTable(TRUE);
|
|
|
|
// Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Expand All ?
|
|
if (m_SortInfo.fExpandAll)
|
|
{
|
|
// Expand Everything
|
|
_ExpandThread(INVALID_ROWINDEX, FALSE, FALSE);
|
|
}
|
|
|
|
// Otherwise, collapse all
|
|
else
|
|
{
|
|
// Collapse Everything
|
|
_CollapseThread(INVALID_ROWINDEX, FALSE);
|
|
}
|
|
}
|
|
|
|
// Set Bit to denote that m_fBuiltTable
|
|
m_fLoaded = TRUE;
|
|
|
|
exit:
|
|
// Free rgMessage?
|
|
for (; i<cFetched; i++)
|
|
{
|
|
// Free this record
|
|
m_pDB->FreeRecord(&rgMessage[i]);
|
|
}
|
|
|
|
// Close the Rowset
|
|
m_pDB->CloseRowset(&hRowset);
|
|
|
|
// Cleanup
|
|
SafeMemFree(Query.pszQuery);
|
|
|
|
// Do OnBegin
|
|
if (pCallback)
|
|
pCallback->OnComplete(SOT_SORTING, S_OK, NULL, NULL);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_SortAndThreadTable
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_SortAndThreadTable(BOOL fApplyFilter)
|
|
{
|
|
// Locals
|
|
DWORD i;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_SortAndThreadTable");
|
|
|
|
// If there are rows...
|
|
if (0 == m_cRows)
|
|
goto exit;
|
|
|
|
// Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Build Thread Roots
|
|
for (i = 0; i < m_cRows; i++)
|
|
{
|
|
// Set pRow
|
|
pRow = m_prgpRow[i];
|
|
|
|
// If Not Filtered...
|
|
if (FALSE == pRow->fFiltered && FALSE == pRow->fHidden)
|
|
{
|
|
// Insert this row into a thread...
|
|
if (S_FALSE == _InsertRowIntoThread(pRow, FALSE))
|
|
{
|
|
// Subject Threading ?
|
|
if (m_pThreadSubject && pRow->Message.pszNormalSubj)
|
|
{
|
|
// Insert Subject into Hash Table...
|
|
m_pThreadSubject->Insert(pRow->Message.pszNormalSubj, (LPVOID)pRow, HF_NO_DUPLICATES);
|
|
}
|
|
|
|
// Visible
|
|
pRow->fVisible = TRUE;
|
|
|
|
// Its a Root
|
|
m_prgpView[m_cView++] = pRow;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show Replies Only ?
|
|
if (fApplyFilter && m_SortInfo.fShowReplies)
|
|
{
|
|
// PruneToReplies
|
|
_PruneToReplies();
|
|
}
|
|
}
|
|
|
|
// If there are rows...
|
|
if (0 == m_cView)
|
|
goto exit;
|
|
|
|
// Sort the View
|
|
_SortView(0, m_cView - 1);
|
|
|
|
// Refresh Filter
|
|
if (fApplyFilter && m_SortInfo.fShowReplies)
|
|
{
|
|
// Refresh Any filter (I have to do this after I've pruned replies
|
|
_RefreshFilter();
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_PruneToReplies
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_PruneToReplies(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD iRow;
|
|
LPROWINFO pRow;
|
|
FOLDERINFO Server={0};
|
|
IImnAccount *pAccount=NULL;
|
|
CHAR szEmail[CCHMAX_EMAIL_ADDRESS];
|
|
THREADISFROMME IsFromMe;
|
|
THREADHIDE HideThread={0};
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_PruneToReplies");
|
|
|
|
// Validate
|
|
Assert(FOLDER_NEWS == m_Folder.tyFolder && TRUE == m_SortInfo.fThreaded);
|
|
|
|
// Free m_pszEmail
|
|
SafeMemFree(m_pszEmail);
|
|
|
|
// Get Folder Store Info
|
|
IF_FAILEXIT(hr = GetFolderStoreInfo(m_Folder.idFolder, &Server));
|
|
|
|
// Better have an account id
|
|
Assert(Server.pszAccountId);
|
|
|
|
// Find the Account for the id for this Server
|
|
IF_FAILEXIT(hr = g_pAcctMan->FindAccount(AP_ACCOUNT_ID, Server.pszAccountId, &pAccount));
|
|
|
|
// Try the NNTP Email Address
|
|
IF_FAILEXIT(hr = pAccount->GetPropSz(AP_NNTP_EMAIL_ADDRESS, szEmail, CCHMAX_EMAIL_ADDRESS));
|
|
|
|
// Duplicate szEmail
|
|
IF_NULLEXIT(m_pszEmail = PszDupA(szEmail));
|
|
|
|
// Don't notify on hide thread
|
|
HideThread.fNotify = FALSE;
|
|
|
|
// Init iRow...
|
|
iRow = 0;
|
|
|
|
// Walk through the Roots
|
|
while (iRow < m_cView)
|
|
{
|
|
// Set pRow
|
|
pRow = m_prgpView[iRow];
|
|
|
|
// Not a Root ?
|
|
if (NULL == pRow->pParent)
|
|
{
|
|
// Reset
|
|
IsFromMe.fResult = FALSE;
|
|
IsFromMe.pRow = NULL;
|
|
|
|
// Find the first message that is from me in this thread...
|
|
_WalkMessageThread(pRow, WALK_THREAD_CURRENT, (DWORD_PTR)&IsFromMe, _WalkThreadIsFromMe);
|
|
|
|
// If Not From Me, then hide this thread...
|
|
if (FALSE == IsFromMe.fResult)
|
|
{
|
|
// Find the first message that is from me in this thread...
|
|
_WalkMessageThread(pRow, WALK_THREAD_CURRENT | WALK_THREAD_BOTTOMUP, (DWORD_PTR)&HideThread, _WalkThreadHide);
|
|
}
|
|
|
|
// Otherwise, increment iRow
|
|
else
|
|
iRow++;
|
|
}
|
|
|
|
// Otherwise, increment iRow
|
|
else
|
|
iRow++;
|
|
}
|
|
|
|
exit:
|
|
// Clearnup
|
|
SafeRelease(pAccount);
|
|
g_pStore->FreeRecord(&Server);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_AdjustUnreadCount
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_AdjustUnreadCount(LPROWINFO pRow, LONG lCount)
|
|
{
|
|
// Not Filtered
|
|
if (FALSE == pRow->fFiltered && FALSE == pRow->fHidden)
|
|
{
|
|
// Not Read
|
|
if (FALSE == ISFLAGSET(pRow->Message.dwFlags, ARF_READ))
|
|
{
|
|
// Adjust Unread Count
|
|
m_cUnread += lCount;
|
|
}
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// SafeStrCmpI
|
|
//--------------------------------------------------------------------------
|
|
inline SafeStrCmpI(LPCSTR psz1, LPCSTR psz2)
|
|
{
|
|
// Null
|
|
if (NULL == psz1)
|
|
{
|
|
// Equal
|
|
if (NULL == psz2)
|
|
return(0);
|
|
|
|
// Less Than
|
|
return(-1);
|
|
}
|
|
|
|
// Greater than
|
|
if (NULL == psz2)
|
|
return(1);
|
|
|
|
// Return Comparison
|
|
return(lstrcmpi(psz1, psz2));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_CompareMessages
|
|
//--------------------------------------------------------------------------
|
|
LONG CMessageTable::_CompareMessages(LPMESSAGEINFO pMsg1, LPMESSAGEINFO pMsg2)
|
|
{
|
|
// Locals
|
|
LONG lRet = 0;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_CompareMessages");
|
|
|
|
switch (m_SortInfo.idColumn)
|
|
{
|
|
case COLUMN_TO:
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayTo, pMsg2->pszDisplayTo);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_FROM:
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_SUBJECT:
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_RECEIVED:
|
|
lRet = CompareFileTime(&pMsg1->ftReceived, &pMsg2->ftReceived);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_SENT:
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_SIZE:
|
|
lRet = (pMsg1->cbMessage - pMsg2->cbMessage);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_FOLDER:
|
|
lRet = SafeStrCmpI(pMsg1->pszFolder, pMsg2->pszFolder);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_LINES:
|
|
lRet = (pMsg1->cLines - pMsg2->cLines);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_ACCOUNT:
|
|
lRet = SafeStrCmpI(pMsg1->pszAcctName, pMsg2->pszAcctName);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftReceived, &pMsg2->ftReceived);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
}
|
|
break;
|
|
|
|
case COLUMN_ATTACHMENT:
|
|
lRet = (pMsg1->dwFlags & ARF_HASATTACH) - (pMsg2->dwFlags & ARF_HASATTACH);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COLUMN_PRIORITY:
|
|
lRet = (pMsg1->wPriority - pMsg2->wPriority);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COLUMN_FLAG:
|
|
lRet = (pMsg1->dwFlags & ARF_FLAGGED) - (pMsg2->dwFlags & ARF_FLAGGED);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COLUMN_DOWNLOADMSG:
|
|
lRet = (pMsg1->dwFlags & ARF_DOWNLOAD) - (pMsg2->dwFlags & ARF_DOWNLOAD);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case COLUMN_THREADSTATE:
|
|
lRet = (pMsg1->dwFlags & (ARF_WATCH | ARF_IGNORE)) - (pMsg2->dwFlags & (ARF_WATCH | ARF_IGNORE));
|
|
if (0 == lRet)
|
|
{
|
|
lRet = CompareFileTime(&pMsg1->ftSent, &pMsg2->ftSent);
|
|
if (0 == lRet)
|
|
{
|
|
lRet = SafeStrCmpI(pMsg1->pszNormalSubj, pMsg2->pszNormalSubj);
|
|
if (0 == lRet)
|
|
lRet = SafeStrCmpI(pMsg1->pszDisplayFrom, pMsg2->pszDisplayFrom);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Assert(FALSE);
|
|
break;
|
|
}
|
|
|
|
// Done
|
|
return (m_SortInfo.fAscending ? lRet : -lRet);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_SortView
|
|
//--------------------------------------------------------------------------
|
|
VOID CMessageTable::_SortView(LONG left, LONG right)
|
|
{
|
|
// Locals
|
|
register LONG i;
|
|
register LONG j;
|
|
LPROWINFO pRow;
|
|
LPROWINFO y;
|
|
|
|
i = left;
|
|
j = right;
|
|
pRow = m_prgpView[(left + right) / 2];
|
|
|
|
do
|
|
{
|
|
while (_CompareMessages(&m_prgpView[i]->Message, &pRow->Message) < 0 && i < right)
|
|
i++;
|
|
while (_CompareMessages(&m_prgpView[j]->Message, &pRow->Message) > 0 && j > left)
|
|
j--;
|
|
|
|
if (i <= j)
|
|
{
|
|
y = m_prgpView[i];
|
|
m_prgpView[i] = m_prgpView[j];
|
|
m_prgpView[j] = y;
|
|
i++; j--;
|
|
}
|
|
} while (i <= j);
|
|
|
|
if (left < j)
|
|
_SortView(left, j);
|
|
if (i < right)
|
|
_SortView(i, right);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetCount
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetCount(GETCOUNTTYPE tyCount, DWORD *pcRows)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
FOLDERID idFolder;
|
|
FOLDERINFO Folder;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetCount");
|
|
|
|
// Invalid Args
|
|
Assert(pcRows);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*pcRows = 0;
|
|
|
|
// Get the Folder Id
|
|
IF_FAILEXIT(hr = m_pFolder->GetFolderId(&idFolder));
|
|
|
|
// Handle Type
|
|
switch(tyCount)
|
|
{
|
|
case MESSAGE_COUNT_VISIBLE:
|
|
*pcRows = m_cView;
|
|
break;
|
|
|
|
case MESSAGE_COUNT_ALL:
|
|
*pcRows = (m_cRows - m_cFiltered);
|
|
break;
|
|
|
|
case MESSAGE_COUNT_FILTERED:
|
|
*pcRows = m_cFiltered;
|
|
break;
|
|
|
|
case MESSAGE_COUNT_UNREAD:
|
|
*pcRows = m_cUnread;
|
|
break;
|
|
|
|
case MESSAGE_COUNT_NOTDOWNLOADED:
|
|
if (SUCCEEDED(g_pStore->GetFolderInfo(idFolder, &Folder)))
|
|
{
|
|
if (Folder.tyFolder == FOLDER_NEWS)
|
|
*pcRows = NewsUtil_GetNotDownloadCount(&Folder);
|
|
g_pStore->FreeRecord(&Folder);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
hr = TraceResult(E_INVALIDARG);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_FreeTable
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_FreeTable(void)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_FreeTable");
|
|
|
|
// Free Hash Tables
|
|
SafeRelease(m_pThreadMsgId);
|
|
SafeRelease(m_pThreadSubject);
|
|
|
|
// Free Elements
|
|
_FreeTableElements();
|
|
|
|
// Fre the Array
|
|
SafeMemFree(m_prgpRow);
|
|
|
|
// Free the View Index
|
|
SafeMemFree(m_prgpView);
|
|
|
|
// Set m_cAllocated
|
|
m_cFiltered = m_cUnread = m_cRows = m_cView = m_cAllocated = 0;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_FreeTableElements
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_FreeTableElements(void)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_FreeTableElements");
|
|
|
|
// If we have an m_prgpRow
|
|
if (m_prgpRow)
|
|
{
|
|
// Free Cache
|
|
for (DWORD i=0; i<m_cRows; i++)
|
|
{
|
|
// Not Null ?
|
|
if (m_prgpRow[i])
|
|
{
|
|
// Release the Row
|
|
ReleaseRow(&m_prgpRow[i]->Message);
|
|
|
|
// Null It
|
|
m_prgpRow[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRow
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetRow(ROWINDEX iRow, LPMESSAGEINFO *ppInfo)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetRow");
|
|
|
|
// Invalid Args
|
|
Assert(ppInfo);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*ppInfo = NULL;
|
|
|
|
// Failure
|
|
hr = _GetRowFromIndex(iRow, &pRow);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
// Copy the Record to pInfo...
|
|
*ppInfo = &pRow->Message;
|
|
|
|
// Increment Refs
|
|
pRow->cRefs++;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::ReleaseRow
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::ReleaseRow(LPMESSAGEINFO pMessage)
|
|
{
|
|
// Locals
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::ReleaseRow");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Release ?
|
|
if (pMessage)
|
|
{
|
|
// Get pRow
|
|
pRow = (LPROWINFO)pMessage->dwReserved;
|
|
|
|
// Must have at least one ref
|
|
IxpAssert(pRow->cRefs);
|
|
|
|
// Decrement Refs
|
|
pRow->cRefs--;
|
|
|
|
// No more refs
|
|
if (0 == pRow->cRefs)
|
|
{
|
|
// Free
|
|
m_pDB->FreeRecord(&pRow->Message);
|
|
|
|
// Free pMessage
|
|
m_pDB->HeapFree(pRow);
|
|
}
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRelativeRow
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetRelativeRow(ROWINDEX iRow, RELATIVEROWTYPE tyRelative,
|
|
LPROWINDEX piRelative)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetRelativeRow");
|
|
|
|
// Invalid Args
|
|
Assert(piRelative);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*piRelative = INVALID_ROWINDEX;
|
|
|
|
// Failure
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// Parent
|
|
if (RELATIVE_ROW_PARENT == tyRelative)
|
|
{
|
|
// If this row is expanded...
|
|
if (TRUE == pRow->fExpanded)
|
|
{
|
|
// Expand...
|
|
_CollapseThread(iRow, TRUE);
|
|
|
|
// Return iRow
|
|
*piRelative = iRow;
|
|
}
|
|
|
|
// If there is a Parent
|
|
else if (pRow->pParent)
|
|
{
|
|
// Get Row Index
|
|
IF_FAILEXIT(hr = GetRowIndex(pRow->pParent->Message.idMessage, piRelative));
|
|
}
|
|
}
|
|
|
|
// Child
|
|
else if (RELATIVE_ROW_CHILD == tyRelative)
|
|
{
|
|
// If there is a Parent
|
|
if (pRow->pChild)
|
|
{
|
|
// If not Expanded, expand...
|
|
if (FALSE == pRow->fExpanded)
|
|
{
|
|
// Expand...
|
|
_ExpandThread(iRow, TRUE, FALSE);
|
|
|
|
// Return iRow
|
|
*piRelative = iRow;
|
|
}
|
|
|
|
// Otherwise...
|
|
else
|
|
{
|
|
// Get Row Index
|
|
IF_FAILEXIT(hr = GetRowIndex(pRow->pChild->Message.idMessage, piRelative));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Root
|
|
else if (RELATIVE_ROW_ROOT == tyRelative)
|
|
{
|
|
// While
|
|
while (pRow->pParent)
|
|
{
|
|
// Walk to the root
|
|
pRow = pRow->pParent;
|
|
}
|
|
|
|
// Get Row Index
|
|
IF_FAILEXIT(hr = GetRowIndex(pRow->Message.idMessage, piRelative));
|
|
}
|
|
|
|
// Failure
|
|
else
|
|
{
|
|
hr = TraceResult(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetLanguage
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetLanguage(ROWINDEX iRow, LPDWORD pdwCodePage)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPMESSAGEINFO pMessage=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetLanguage");
|
|
|
|
// Invalid Args
|
|
Assert(pdwCodePage);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Get the Row
|
|
IF_FAILEXIT(hr = GetRow(iRow, &pMessage));
|
|
|
|
// Get the Charset
|
|
*pdwCodePage = pMessage->wLanguage;
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeReleaseRow(this, pMessage);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::SetLanguage
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::SetLanguage(DWORD cRows, LPROWINDEX prgiRow,
|
|
DWORD dwCodePage)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD i;
|
|
HLOCK hLock=NULL;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::SetLanguage");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Lock Notify
|
|
IF_FAILEXIT(hr = m_pDB->Lock(&hLock));
|
|
|
|
// Loop
|
|
for (i=0; i<cRows; i++)
|
|
{
|
|
// Get Row
|
|
if (SUCCEEDED(_GetRowFromIndex(prgiRow[i], &pRow)))
|
|
{
|
|
// Set the Language
|
|
pRow->Message.wLanguage = (WORD)dwCodePage;
|
|
|
|
// Update the Record
|
|
IF_FAILEXIT(hr = m_pDB->UpdateRecord(&pRow->Message));
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Lock Notify
|
|
m_pDB->Unlock(&hLock);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::OpenMessage
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::OpenMessage(ROWINDEX iRow, OPENMESSAGEFLAGS dwFlags,
|
|
IMimeMessage **ppMessage, IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPMESSAGEINFO pMessage=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetMessage");
|
|
|
|
// Invalid Args
|
|
Assert(ppMessage);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*ppMessage = NULL;
|
|
|
|
// Get the message info
|
|
IF_FAILEXIT(hr = GetRow(iRow, &pMessage));
|
|
|
|
// Open the message
|
|
IF_FAILEXIT(hr = m_pFolder->OpenMessage(pMessage->idMessage, dwFlags, ppMessage, pCallback));
|
|
|
|
exit:
|
|
// Clenaup
|
|
SafeReleaseRow(this, pMessage);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRowMessageId
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetRowMessageId(ROWINDEX iRow, LPMESSAGEID pidMessage)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPMESSAGEINFO pMessage=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetRowMessageId");
|
|
|
|
// Invalid Args
|
|
Assert(pidMessage);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*pidMessage = 0;
|
|
|
|
// Get the Row Info
|
|
IF_FAILEXIT(hr = GetRow(iRow, &pMessage));
|
|
|
|
// Store the id
|
|
*pidMessage = pMessage->idMessage;
|
|
|
|
exit:
|
|
// Free
|
|
SafeReleaseRow(this, pMessage);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRowIndex
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetRowIndex(MESSAGEID idMessage, LPROWINDEX piRow)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ROWINDEX iRow;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetRowIndex");
|
|
|
|
// Invalid Args
|
|
Assert(idMessage && piRow);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// INit
|
|
*piRow = INVALID_ROWINDEX;
|
|
|
|
// Loop through the view index
|
|
for (iRow=0; iRow<m_cView; iRow++)
|
|
{
|
|
// Is This It ?
|
|
if (m_prgpView[iRow]->Message.idMessage == idMessage)
|
|
{
|
|
// Done
|
|
*piRow = iRow;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Not Found
|
|
hr = DB_E_NOTFOUND;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetIndentLevel
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetIndentLevel(ROWINDEX iRow, LPDWORD pcIndent)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetIndentLevel");
|
|
|
|
// Invalid Args
|
|
Assert(pcIndent);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Don't Call Unless Threaded
|
|
Assert(m_SortInfo.fThreaded);
|
|
|
|
// Init
|
|
*pcIndent = 0;
|
|
|
|
// Valid irow
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// Walk the Parent Chain...
|
|
while (pRow->pParent)
|
|
{
|
|
// Increment Index
|
|
(*pcIndent)++;
|
|
|
|
// Set pRow
|
|
pRow = pRow->pParent;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkMessageThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkMessageThread(LPROWINFO pRow, WALKTHREADFLAGS dwFlags,
|
|
DWORD_PTR dwCookie, PFWALKTHREADCALLBACK pfnCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pCurrent;
|
|
LPROWINFO pTemp;
|
|
BOOL fCurrent=FALSE;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkMessageThread");
|
|
|
|
// Invalid Args
|
|
Assert(pfnCallback);
|
|
|
|
// Include idMessage ?
|
|
if (ISFLAGSET(dwFlags, WALK_THREAD_CURRENT))
|
|
{
|
|
// This is the first iteration
|
|
fCurrent = TRUE;
|
|
}
|
|
|
|
// Don't include current anymore
|
|
FLAGCLEAR(dwFlags, WALK_THREAD_CURRENT);
|
|
|
|
// Bottom Up Recursion...
|
|
if (ISFLAGSET(dwFlags, WALK_THREAD_BOTTOMUP))
|
|
{
|
|
// Set iCurrent
|
|
pCurrent = pRow->pChild;
|
|
|
|
// Loop
|
|
while (pCurrent)
|
|
{
|
|
// Enumerate Children
|
|
IF_FAILEXIT(hr = _WalkMessageThread(pCurrent, dwFlags, dwCookie, pfnCallback));
|
|
|
|
// Set iCurrent
|
|
pTemp = pCurrent->pSibling;
|
|
|
|
// Call the Callback
|
|
(*(pfnCallback))(this, pCurrent, dwCookie);
|
|
|
|
// Set pCurrent
|
|
pCurrent = pTemp;
|
|
}
|
|
|
|
// Can't Support these flags with bottom up...
|
|
if (TRUE == fCurrent)
|
|
{
|
|
// Call the Callback
|
|
(*(pfnCallback))(this, pRow, dwCookie);
|
|
}
|
|
}
|
|
|
|
// Otherwise.
|
|
else
|
|
{
|
|
// Include idMessage ?
|
|
if (TRUE == fCurrent)
|
|
{
|
|
// Call the Callback
|
|
(*(pfnCallback))(this, pRow, dwCookie);
|
|
}
|
|
|
|
// Set iCurrent
|
|
pCurrent = pRow->pChild;
|
|
|
|
// Loop
|
|
while (pCurrent)
|
|
{
|
|
// Call the Callback
|
|
(*(pfnCallback))(this, pCurrent, dwCookie);
|
|
|
|
// Enumerate Children
|
|
IF_FAILEXIT(hr = _WalkMessageThread(pCurrent, dwFlags, dwCookie, pfnCallback));
|
|
|
|
// Set iCurrent
|
|
pCurrent = pCurrent->pSibling;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetSelectionState
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetSelectionState(DWORD cRows, LPROWINDEX prgiRow,
|
|
SELECTIONSTATE dwMask, BOOL fIncludeChildren, SELECTIONSTATE *pdwState)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
FOLDERID idFolder;
|
|
FOLDERINFO Folder={0};
|
|
LPROWINFO pRow;
|
|
FOLDERTYPE tyFolder;
|
|
DWORD i;
|
|
GETSELECTIONSTATE Selection={0};
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetSelectionState");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*pdwState = 0;
|
|
|
|
// SELECTION_STATE_DELETABLE
|
|
if (ISFLAGSET(dwMask, SELECTION_STATE_DELETABLE))
|
|
{
|
|
// Not a Find Folder ?
|
|
if (NULL == m_pFindFolder)
|
|
{
|
|
// Get the Folder Id from pidFolder
|
|
IF_FAILEXIT(hr = m_pFolder->GetFolderId(&idFolder));
|
|
|
|
// Get Folder Info
|
|
IF_FAILEXIT(hr = g_pStore->GetFolderInfo(idFolder, &Folder));
|
|
|
|
// BUGBUG @bug [PaulHi] 4/23/99 This is backwards. The FOLDER_NEWS is the only folder
|
|
// that CAN'T delete messages. The CMessageList::_IsSelectionDeletable() function
|
|
// reverses this so that deletion is available correctly. I don't want to mess with
|
|
// this now, in case other code compensates for this.
|
|
// $HACK$ We know that the only folder types that can delete messages are FOLDER_NEWS
|
|
if (FOLDER_NEWS == Folder.tyFolder)
|
|
{
|
|
// Set the Flag
|
|
FLAGSET(*pdwState, SELECTION_STATE_DELETABLE);
|
|
}
|
|
|
|
#if 0
|
|
// [PaulHi] 4/25/99 Only HotMail HTTP servers don't allow deletion of items in the
|
|
// 'deleted' folders. Excehange servers do, so back this fix out.
|
|
// [PaulHi] 4/23/99 Raid 62883.
|
|
if ( (FOLDER_HTTPMAIL == Folder.tyFolder) && (FOLDER_DELETED == Folder.tySpecial) )
|
|
{
|
|
FLAGSET(*pdwState, SELECTION_STATE_DELETABLE); // Not deletable see above @bug comment
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Otherwise, ask the find folder...
|
|
else
|
|
{
|
|
// Setup Selection
|
|
Selection.dwMask = dwMask;
|
|
Selection.dwState = 0;
|
|
|
|
// Mark things that are in this folder...
|
|
for (i=0; i<cRows; i++)
|
|
{
|
|
// Good Row Index
|
|
if (SUCCEEDED(_GetRowFromIndex(prgiRow[i], &pRow)))
|
|
{
|
|
// Get the Folder Type
|
|
IF_FAILEXIT(hr = m_pFindFolder->GetMessageFolderType(pRow->Message.idMessage, &tyFolder));
|
|
|
|
// Get the State
|
|
if (FOLDER_NEWS == tyFolder)
|
|
{
|
|
// Set the State
|
|
FLAGSET(*pdwState, SELECTION_STATE_DELETABLE);
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
|
|
// Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Do Children ?
|
|
if (fIncludeChildren && !pRow->fExpanded && pRow->pChild)
|
|
{
|
|
// Walk the Thread
|
|
IF_FAILEXIT(hr = _WalkMessageThread(pRow, NOFLAGS, (DWORD_PTR)&Selection, _WalkThreadGetSelectionState));
|
|
|
|
// Optimize so that we can finish early
|
|
if (ISFLAGSET(Selection.dwState, SELECTION_STATE_DELETABLE))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Free
|
|
g_pStore->FreeRecord(&Folder);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_IsThreadImportance
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_IsThreadImportance(LPROWINFO pRow, MESSAGEFLAGS dwFlag,
|
|
ROWSTATE dwState, ROWSTATE *pdwState)
|
|
{
|
|
// Locals
|
|
LPROWINFO pRoot;
|
|
GETTHREADSTATE GetState={0};
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_IsThreadImportance");
|
|
|
|
// Validate
|
|
Assert(ARF_WATCH == dwFlag || ARF_IGNORE == dwFlag);
|
|
|
|
// Does this row have the flag set ?
|
|
if (ISFLAGSET(pRow->Message.dwFlags, dwFlag))
|
|
{
|
|
// Set the State
|
|
FLAGSET(*pdwState, dwState);
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
// Get the Root of this thread
|
|
pRoot = _PGetThreadRoot(pRow);
|
|
|
|
// Does this row have the flag set ?
|
|
if (ISFLAGSET(pRoot->Message.dwFlags, dwFlag))
|
|
{
|
|
// Set the State
|
|
FLAGSET(*pdwState, dwState);
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
// Set Flags to Count
|
|
GetState.dwFlags = dwFlag;
|
|
|
|
// Enumerate Immediate Children
|
|
_WalkMessageThread(pRoot, NOFLAGS, (DWORD_PTR)&GetState, _WalkThreadGetState);
|
|
|
|
// If This is row is marked as read
|
|
if (GetState.cHasFlags > 0)
|
|
{
|
|
// Set the Bit
|
|
FLAGSET(*pdwState, dwState);
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
// Not Found
|
|
return(S_FALSE);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRowState
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetRowState(ROWINDEX iRow, ROWSTATE dwStateMask,
|
|
ROWSTATE *pdwState)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetRowState");
|
|
|
|
// Invalid Args
|
|
Assert(pdwState);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this) || iRow >= m_cRows)
|
|
return(E_UNEXPECTED);
|
|
|
|
// Initialzie
|
|
*pdwState = 0;
|
|
|
|
// Get the row
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// Is the state Cached Yet?
|
|
if (ISFLAGSET(pRow->dwState, ROW_STATE_VALID))
|
|
{
|
|
// Return the State
|
|
*pdwState = pRow->dwState;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
// Reset
|
|
pRow->dwState = 0;
|
|
|
|
// Get Thread State
|
|
if (m_SortInfo.fThreaded && pRow->pChild && !pRow->fExpanded && ISFLAGSET(pRow->Message.dwFlags, ARF_READ))
|
|
{
|
|
// Locals
|
|
GETTHREADSTATE GetState={0};
|
|
|
|
// Set Flags to Count
|
|
GetState.dwFlags = ARF_READ;
|
|
|
|
// Enumerate Immediate Children
|
|
_WalkMessageThread(pRow, NOFLAGS, (DWORD_PTR)&GetState, _WalkThreadGetState);
|
|
|
|
// If This is row is marked as read
|
|
if (GetState.cHasFlags == GetState.cChildren)
|
|
FLAGSET(pRow->dwState, ROW_STATE_READ);
|
|
}
|
|
|
|
// Otherwise, just check the message
|
|
else if (ISFLAGSET(pRow->Message.dwFlags, ARF_READ))
|
|
FLAGSET(pRow->dwState, ROW_STATE_READ);
|
|
|
|
// If single watched row
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_WATCH))
|
|
FLAGSET(pRow->dwState, ROW_STATE_WATCHED);
|
|
|
|
// If single ignored row
|
|
else if (ISFLAGSET(pRow->Message.dwFlags, ARF_IGNORE))
|
|
FLAGSET(pRow->dwState, ROW_STATE_IGNORED);
|
|
|
|
// ROW_STATE_DELETED
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_ENDANGERED) || ISFLAGSET(pRow->Message.dwFlags, ARF_ARTICLE_EXPIRED))
|
|
FLAGSET(pRow->dwState, ROW_STATE_DELETED);
|
|
|
|
// ROW_STATE_HAS_BODY
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_HASBODY))
|
|
FLAGSET(pRow->dwState, ROW_STATE_HAS_BODY);
|
|
|
|
// ROW_STATE_FLAGGED
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_FLAGGED))
|
|
FLAGSET(pRow->dwState, ROW_STATE_FLAGGED);
|
|
|
|
// ROW_STATE_EXPANDED
|
|
if (m_SortInfo.fThreaded && pRow->fExpanded)
|
|
FLAGSET(pRow->dwState, ROW_STATE_EXPANDED);
|
|
|
|
// ROW_STATE_HAS_CHILDREN
|
|
if (m_SortInfo.fThreaded && pRow->pChild)
|
|
FLAGSET(pRow->dwState, ROW_STATE_HAS_CHILDREN);
|
|
|
|
// ROW_STATE_MARKED_DOWNLOAD
|
|
if (ISFLAGSET(pRow->Message.dwFlags, ARF_DOWNLOAD))
|
|
FLAGSET(pRow->dwState, ROW_STATE_MARKED_DOWNLOAD);
|
|
|
|
// Cache the State
|
|
FLAGSET(pRow->dwState, ROW_STATE_VALID);
|
|
|
|
// Return the State
|
|
*pdwState = pRow->dwState;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Mark
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::Mark(LPROWINDEX prgiRow, DWORD cRows,
|
|
APPLYCHILDRENTYPE tyApply, MARK_TYPE tyMark, IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG i;
|
|
LPMESSAGEINFO pMessage=NULL;
|
|
ADJUSTFLAGS Flags={0};
|
|
MESSAGEIDLIST List={0};
|
|
LPROWINFO pRow;
|
|
HCURSOR hCursor=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::Mark");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Handle Mark Type
|
|
switch(tyMark)
|
|
{
|
|
case MARK_MESSAGE_READ:
|
|
Flags.dwAdd = ARF_READ;
|
|
break;
|
|
|
|
case MARK_MESSAGE_UNREAD:
|
|
Flags.dwRemove = ARF_READ;
|
|
break;
|
|
|
|
case MARK_MESSAGE_DELETED:
|
|
Flags.dwAdd = ARF_ENDANGERED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_UNDELETED:
|
|
Flags.dwRemove = ARF_ENDANGERED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_DOWNLOAD:
|
|
Flags.dwAdd = ARF_DOWNLOAD;
|
|
break;
|
|
|
|
case MARK_MESSAGE_UNDOWNLOAD:
|
|
Flags.dwRemove = ARF_DOWNLOAD;
|
|
break;
|
|
|
|
case MARK_MESSAGE_FLAGGED:
|
|
Flags.dwAdd = ARF_FLAGGED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_UNFLAGGED:
|
|
Flags.dwRemove = ARF_FLAGGED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_FORWARDED:
|
|
Flags.dwAdd = ARF_FORWARDED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_UNFORWARDED:
|
|
Flags.dwRemove = ARF_FORWARDED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_REPLIED:
|
|
Flags.dwAdd = ARF_REPLIED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_UNREPLIED:
|
|
Flags.dwRemove = ARF_REPLIED;
|
|
break;
|
|
|
|
case MARK_MESSAGE_NOSECUI:
|
|
Flags.dwAdd = ARF_NOSECUI;
|
|
break;
|
|
|
|
case MARK_MESSAGE_SECUI:
|
|
Flags.dwRemove = ARF_NOSECUI;
|
|
break;
|
|
|
|
case MARK_MESSAGE_WATCH:
|
|
Flags.dwAdd = ARF_WATCH;
|
|
Flags.dwRemove = ARF_IGNORE;
|
|
break;
|
|
|
|
case MARK_MESSAGE_IGNORE:
|
|
Flags.dwAdd = ARF_IGNORE;
|
|
Flags.dwRemove = ARF_WATCH;
|
|
break;
|
|
|
|
case MARK_MESSAGE_NORMALTHREAD:
|
|
Flags.dwRemove = ARF_WATCH | ARF_IGNORE;
|
|
break;
|
|
|
|
case MARK_MESSAGE_RCPT_PROCESSED:
|
|
Flags.dwAdd = ARF_RCPT_PROCESSED;
|
|
break;
|
|
|
|
default:
|
|
Assert(FALSE);
|
|
hr = TraceResult(E_INVALIDARG);
|
|
goto exit;
|
|
}
|
|
|
|
// Wait Cursor
|
|
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
// Not Mark All...
|
|
if (prgiRow && cRows)
|
|
{
|
|
// Allocate an Array
|
|
IF_FAILEXIT(hr = _GrowIdList(&List, cRows + 32));
|
|
|
|
// Mark things that are in this folder...
|
|
for (i=0; i<cRows; i++)
|
|
{
|
|
// Valid Row Index
|
|
if (SUCCEEDED(_GetRowFromIndex(prgiRow[i], &pRow)))
|
|
{
|
|
// Allocate an Array
|
|
IF_FAILEXIT(hr = _GrowIdList(&List, 1));
|
|
|
|
// Set id
|
|
List.prgidMsg[List.cMsgs++] = pRow->Message.idMessage;
|
|
|
|
// Do the children
|
|
if (APPLY_CHILDREN == tyApply || (APPLY_COLLAPSED == tyApply && !pRow->fExpanded))
|
|
{
|
|
// Only if there are children
|
|
if (pRow->pChild)
|
|
{
|
|
// Walk the Thread
|
|
IF_FAILEXIT(hr = _WalkMessageThread(pRow, NOFLAGS, (DWORD_PTR)&List, _WalkThreadGetIdList));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Are there messages
|
|
if (List.cMsgs > 0)
|
|
{
|
|
// Adjust the Flags
|
|
IF_FAILEXIT(hr = m_pFolder->SetMessageFlags(&List, &Flags, NULL, pCallback));
|
|
}
|
|
}
|
|
|
|
// Mark All
|
|
else
|
|
{
|
|
// Adjust the Flags
|
|
IF_FAILEXIT(hr = m_pFolder->SetMessageFlags(NULL, &Flags, NULL, pCallback));
|
|
}
|
|
|
|
// Re-Register for notifications
|
|
m_pDB->DispatchNotify((IDatabaseNotify *)this);
|
|
|
|
exit:
|
|
// Reset Cursor
|
|
SetCursor(hCursor);
|
|
|
|
// Cleanup
|
|
SafeMemFree(List.prgidMsg);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::ConnectionRelease
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::ConnectionAddRef(void)
|
|
{
|
|
if (m_pFolder)
|
|
m_pFolder->ConnectionAddRef();
|
|
return S_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::ConnectionRelease
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::ConnectionRelease(void)
|
|
{
|
|
if (m_pFolder)
|
|
m_pFolder->ConnectionRelease();
|
|
return S_OK;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Synchronize
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::Synchronize(SYNCFOLDERFLAGS dwFlags,
|
|
DWORD cHeaders,
|
|
IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::Synchronize");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Tell the Folder to Synch
|
|
hr = m_pFolder->Synchronize(dwFlags, cHeaders, pCallback);
|
|
|
|
// Success
|
|
if (E_PENDING == hr)
|
|
{
|
|
// We are synching
|
|
m_fSynching = TRUE;
|
|
}
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::SetOwner
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::SetOwner(IStoreCallback *pDefaultCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::SetOwner");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Set the Owner
|
|
hr = m_pFolder->SetOwner(pDefaultCallback);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Close
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::Close(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr = S_OK;
|
|
|
|
//Trace
|
|
TraceCall("CMessageTable::Close");
|
|
|
|
// Pass it on
|
|
if (m_pFolder)
|
|
hr = m_pFolder->Close();
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRowFolderId
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetRowFolderId(ROWINDEX iRow, LPFOLDERID pidFolder)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPMESSAGEINFO pMessage=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetRowFolderId");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Not a Find Folder ?
|
|
if (NULL == m_pFindFolder)
|
|
{
|
|
// Get the Folder Id from pidFolder
|
|
IF_FAILEXIT(hr = m_pFolder->GetFolderId(pidFolder));
|
|
}
|
|
|
|
// Otherwise, ask the find folder...
|
|
else
|
|
{
|
|
// Get the Row
|
|
IF_FAILEXIT(hr = GetRow(iRow, &pMessage));
|
|
|
|
// Call into the find folder
|
|
IF_FAILEXIT(hr = m_pFindFolder->GetMessageFolderId(pMessage->idMessage, pidFolder));
|
|
}
|
|
|
|
exit:
|
|
// Free the Row
|
|
SafeReleaseRow(this, pMessage);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::RegisterNotify
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::RegisterNotify(REGISTERNOTIFYFLAGS dwFlags,
|
|
IMessageTableNotify *pNotify)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::RegisterNotify");
|
|
|
|
// Invalid Args
|
|
if (NULL == pNotify)
|
|
return TraceResult(E_INVALIDARG);
|
|
|
|
// Only One is allowed
|
|
AssertSz(NULL == m_pNotify, "Only one person can register for notifications on my object");
|
|
|
|
// Save It
|
|
m_pNotify = pNotify;
|
|
|
|
// No Release
|
|
m_fRelNotify = FALSE;
|
|
|
|
// AddRef ?
|
|
if (FALSE == ISFLAGSET(dwFlags, REGISTER_NOTIFY_NOADDREF))
|
|
{
|
|
m_pNotify->AddRef();
|
|
m_fRelNotify = TRUE;
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::UnregisterNotify
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::UnregisterNotify(IMessageTableNotify *pNotify)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::UnregisterNotify");
|
|
|
|
// Invalid Args
|
|
if (NULL == pNotify)
|
|
return TraceResult(E_INVALIDARG);
|
|
|
|
// Otherwise, remove
|
|
if (m_pNotify)
|
|
{
|
|
// Validate
|
|
Assert(m_pNotify == pNotify);
|
|
|
|
// Release It
|
|
if (m_fRelNotify)
|
|
m_pNotify->Release();
|
|
m_pNotify = NULL;
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetNextRow
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetNextRow(ROWINDEX iCurrentRow,
|
|
GETNEXTTYPE tyDirection, ROWMESSAGETYPE tyMessage, GETNEXTFLAGS dwFlags,
|
|
LPROWINDEX piNewRow)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ROWINDEX iRow=iCurrentRow;
|
|
ROWINDEX iStartRow=iCurrentRow;
|
|
BOOL fWrapAround=FALSE;
|
|
BYTE fThreadHasUnread;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetNextRow");
|
|
|
|
// Invalid Args
|
|
Assert(piNewRow);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this) || iCurrentRow >= m_cView)
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*piNewRow = INVALID_ROWINDEX;
|
|
|
|
// Loop
|
|
while (1)
|
|
{
|
|
// Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Get pRow
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// If not expanded
|
|
if (FALSE == pRow->fExpanded && pRow->pChild)
|
|
{
|
|
// Imay need to expand this row...
|
|
if (ROWMSG_ALL == tyMessage || (ROWMSG_NEWS == tyMessage && ISFLAGSET(pRow->Message.dwFlags, ARF_NEWSMSG)) || (ROWMSG_MAIL == tyMessage && !ISFLAGSET(pRow->Message.dwFlags, ARF_NEWSMSG)))
|
|
{
|
|
// If looking for unread, see if the thread has unread messages in it
|
|
if (ISFLAGSET(dwFlags, GETNEXT_UNREAD) && !ISFLAGSET(dwFlags, GETNEXT_THREAD))
|
|
{
|
|
// Locals
|
|
GETTHREADSTATE GetState={0};
|
|
|
|
// Set Flags to Count
|
|
GetState.dwFlags = ARF_READ;
|
|
|
|
// Root that isn't totally read...
|
|
_WalkMessageThread(pRow, NOFLAGS, (DWORD_PTR)&GetState, _WalkThreadGetState);
|
|
|
|
// If there are unread children of this
|
|
if (GetState.cHasFlags != GetState.cChildren)
|
|
{
|
|
// Expand This thread
|
|
_ExpandThread(iRow, TRUE, FALSE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Next ?
|
|
if (GETNEXT_NEXT == tyDirection)
|
|
{
|
|
// Increment
|
|
iRow++;
|
|
|
|
// Start back at zero
|
|
if (iRow >= m_cView)
|
|
{
|
|
// Done
|
|
if (!ISFLAGSET(dwFlags, GETNEXT_UNREAD))
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
// We Wrapped Around
|
|
fWrapAround = TRUE;
|
|
|
|
// Start back at zero
|
|
iRow = 0;
|
|
}
|
|
}
|
|
|
|
// Otherwise, backwards
|
|
else
|
|
{
|
|
// Start back at zero
|
|
if (0 == iRow)
|
|
{
|
|
// Done
|
|
if (!ISFLAGSET(dwFlags, GETNEXT_UNREAD))
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
|
|
// We Wrapped Around
|
|
fWrapAround = TRUE;
|
|
|
|
// Start back at zero
|
|
iRow = m_cView - 1;
|
|
}
|
|
|
|
// Otherwise, decrement iRow
|
|
else
|
|
iRow--;
|
|
}
|
|
|
|
// Wrapped and back to original row
|
|
if (fWrapAround && iRow == iStartRow)
|
|
break;
|
|
|
|
// Validate iRow
|
|
Assert(iRow < m_cView);
|
|
|
|
// Get pRow
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// Good time to Stop ?
|
|
if (ROWMSG_ALL == tyMessage || (ROWMSG_NEWS == tyMessage && ISFLAGSET(pRow->Message.dwFlags, ARF_NEWSMSG)) || (ROWMSG_MAIL == tyMessage && !ISFLAGSET(pRow->Message.dwFlags, ARF_NEWSMSG)))
|
|
{
|
|
// Set fThreadHasUnread
|
|
fThreadHasUnread = FALSE;
|
|
|
|
// If looking for unread, see if the thread has unread messages in it
|
|
if (ISFLAGSET(dwFlags, GETNEXT_UNREAD))
|
|
{
|
|
// Locals
|
|
GETTHREADSTATE GetState={0};
|
|
|
|
// Set Flags to Count
|
|
GetState.dwFlags = ARF_READ;
|
|
|
|
// Root that isn't totally read...
|
|
_WalkMessageThread(pRow, NOFLAGS, (DWORD_PTR)&GetState, _WalkThreadGetState);
|
|
|
|
// If there are unread children of this
|
|
if (GetState.cHasFlags != GetState.cChildren)
|
|
{
|
|
// This thread has unread stuff
|
|
fThreadHasUnread = TRUE;
|
|
}
|
|
}
|
|
|
|
// Looking for the next thread with unread messages in it
|
|
if (ISFLAGSET(dwFlags, GETNEXT_THREAD) && ISFLAGSET(dwFlags, GETNEXT_UNREAD))
|
|
{
|
|
// If this is a root thread...
|
|
if (NULL == pRow->pParent)
|
|
{
|
|
// If this row is unread
|
|
if (!ISFLAGSET(pRow->Message.dwFlags, ARF_READ))
|
|
{
|
|
// This is It
|
|
*piNewRow = iRow;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Otherwise...
|
|
else if (fThreadHasUnread)
|
|
{
|
|
// This is It
|
|
*piNewRow = iRow;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Looking for a thread root
|
|
else if (ISFLAGSET(dwFlags, GETNEXT_THREAD) && !ISFLAGSET(dwFlags, GETNEXT_UNREAD))
|
|
{
|
|
// If this is a root thread...
|
|
if (NULL == pRow->pParent)
|
|
{
|
|
// This is It
|
|
*piNewRow = iRow;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Looking for the next unread message
|
|
else if (!ISFLAGSET(dwFlags, GETNEXT_THREAD) && ISFLAGSET(dwFlags, GETNEXT_UNREAD))
|
|
{
|
|
// If this is a thread that has unread children, then expand It.
|
|
if (m_SortInfo.fThreaded && FALSE == pRow->fExpanded && pRow->pChild && fThreadHasUnread)
|
|
{
|
|
// Expand This thread
|
|
_ExpandThread(iRow, TRUE, FALSE);
|
|
}
|
|
|
|
// If this is a root thread...
|
|
if (FALSE == ISFLAGSET(pRow->Message.dwFlags, ARF_READ))
|
|
{
|
|
// This is It
|
|
*piNewRow = iRow;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Otherwise, this is it
|
|
else
|
|
{
|
|
// This is It
|
|
*piNewRow = iRow;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetMessageIdList
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::GetMessageIdList(BOOL fRootsOnly, DWORD cRows,
|
|
LPROWINDEX prgiRow, LPMESSAGEIDLIST pIdList)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD i;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetMessageIdList");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
ZeroMemory(pIdList, sizeof(MESSAGEIDLIST));
|
|
|
|
// Allocate an Array
|
|
IF_FAILEXIT(hr = _GrowIdList(pIdList, cRows + 32));
|
|
|
|
// Mark things that are in this folder...
|
|
for (i=0; i<cRows; i++)
|
|
{
|
|
// Good View Index
|
|
if (SUCCEEDED(_GetRowFromIndex(prgiRow[i], &pRow)))
|
|
{
|
|
// _GrowIdList
|
|
IF_FAILEXIT(hr = _GrowIdList(pIdList, 1));
|
|
|
|
// Set id
|
|
pIdList->prgidMsg[pIdList->cMsgs++] = pRow->Message.idMessage;
|
|
|
|
// If Not Expanded and Has children, insert the children...
|
|
if (!fRootsOnly && m_SortInfo.fThreaded && !pRow->fExpanded && pRow->pChild)
|
|
{
|
|
// Walk the Thread
|
|
IF_FAILEXIT(hr = _WalkMessageThread(pRow, NOFLAGS, (DWORD_PTR)pIdList, _WalkThreadGetIdList));
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
#if 0
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_GetRowOrdinal
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_GetRowOrdinal(MESSAGEID idMessage, LPROWORDINAL piOrdinal)
|
|
{
|
|
// Locals
|
|
LONG lLower=0;
|
|
LONG lUpper=m_cRows - 1;
|
|
LONG lCompare;
|
|
DWORD dwMiddle;
|
|
LPROWINFO pRow;
|
|
|
|
// Do binary search / insert
|
|
while (lLower <= lUpper)
|
|
{
|
|
// Set lMiddle
|
|
dwMiddle = (DWORD)((lLower + lUpper) / 2);
|
|
|
|
// Compute middle record to compare against
|
|
pRow = m_prgpRow[dwMiddle];
|
|
|
|
// Get string to compare against
|
|
lCompare = ((DWORD)idMessage - (DWORD)pRow->Message.idMessage);
|
|
|
|
// If Equal, then were done
|
|
if (lCompare == 0)
|
|
{
|
|
*piOrdinal = dwMiddle;
|
|
return(S_OK);
|
|
}
|
|
|
|
// Compute upper and lower
|
|
if (lCompare > 0)
|
|
lLower = (LONG)(dwMiddle + 1);
|
|
else
|
|
lUpper = (LONG)(dwMiddle - 1);
|
|
}
|
|
|
|
// Not Found
|
|
return(TraceResult(DB_E_NOTFOUND));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_ProcessResults
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_ProcessResults(TRANSACTIONTYPE tyTransaction,
|
|
DWORD cRows, LPROWINDEX prgiRow, LPRESULTLIST pResults)
|
|
{
|
|
// Locals
|
|
DWORD i;
|
|
ROWORDINAL iOrdinal;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_ProcessResults");
|
|
|
|
// Validate
|
|
Assert(TRANSACTION_UPDATE == tyTransaction || TRANSACTION_DELETE == tyTransaction);
|
|
|
|
// Another Validation
|
|
Assert(cRows == pResults->cMsgs);
|
|
|
|
// No Results
|
|
if (NULL == pResults || NULL == pResults->prgResult)
|
|
return(S_OK);
|
|
|
|
// Do Row Updates Myself...
|
|
for (i=0; i<pResults->cValid; i++)
|
|
{
|
|
// If this row was deleted
|
|
if (S_OK == pResults->prgResult[i].hrResult)
|
|
{
|
|
// Get Row From Index
|
|
if (SUCCEEDED(_GetRowFromIndex(prgiRow[i], &pRow)))
|
|
{
|
|
// Validate
|
|
Assert(pResults->prgResult[i].idMessage == pRow->Message.idMessage);
|
|
|
|
// Find the Row Ordinal
|
|
SideAssert(SUCCEEDED(_GetRowOrdinal(pRow->Message.idMessage, &iOrdinal)));
|
|
|
|
// We better have found it
|
|
Assert(iOrdinal < m_cRows);
|
|
|
|
// Update
|
|
else if (TRANSACTION_UPDATE == tyTransaction)
|
|
{
|
|
// Get pRow
|
|
_RowTableUpdate(iOrdinal, &pRow->Message, &pResults->prgResult[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flush Notification Queue
|
|
_FlushNotificationQueue(TRUE);
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
#endif
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::DeleteRows
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::DeleteRows(DELETEMESSAGEFLAGS dwFlags, DWORD cRows,
|
|
LPROWINDEX prgiRow, BOOL fIncludeChildren, IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
MESSAGEIDLIST List={0};
|
|
HCURSOR hCursor=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::DeleteRows");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Wait Cursor
|
|
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
// Get MessageID List
|
|
IF_FAILEXIT(hr = GetMessageIdList((FALSE == fIncludeChildren), cRows, prgiRow, &List));
|
|
|
|
// Adjust the Flags
|
|
IF_FAILEXIT(hr = m_pFolder->DeleteMessages(dwFlags, &List, NULL, pCallback));
|
|
|
|
// Re-Register for notifications
|
|
m_pDB->DispatchNotify((IDatabaseNotify *)this);
|
|
|
|
exit:
|
|
// Reset Cursor
|
|
SetCursor(hCursor);
|
|
|
|
// Cleanup
|
|
SafeMemFree(List.prgidMsg);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::CopyRows
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::CopyRows(FOLDERID idFolder,
|
|
COPYMESSAGEFLAGS dwOptions, DWORD cRows, LPROWINDEX prgiRow,
|
|
LPADJUSTFLAGS pFlags, IStoreCallback *pCallback)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
MESSAGEIDLIST List={0};
|
|
HCURSOR hCursor=NULL;
|
|
IMessageFolder *pDstFolder=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::CopyRows");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Wait Cursor
|
|
hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
|
|
|
|
// Open the Destination Folder
|
|
IF_FAILEXIT(hr = g_pStore->OpenFolder(idFolder, NULL, NOFLAGS, &pDstFolder));
|
|
|
|
// Get MessageID List
|
|
IF_FAILEXIT(hr = GetMessageIdList(FALSE, cRows, prgiRow, &List));
|
|
|
|
// Adjust the Flags
|
|
IF_FAILEXIT(hr = m_pFolder->CopyMessages(pDstFolder, dwOptions, &List, pFlags, NULL, pCallback));
|
|
|
|
// Re-Register for notifications
|
|
m_pDB->DispatchNotify((IDatabaseNotify *)this);
|
|
|
|
exit:
|
|
// Reset Cursor
|
|
SetCursor(hCursor);
|
|
|
|
// Cleanup
|
|
SafeRelease(pDstFolder);
|
|
SafeMemFree(List.prgidMsg);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::QueryService
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::QueryService(REFGUID guidService, REFIID riid, LPVOID *ppvObject)
|
|
{
|
|
// Locals
|
|
HRESULT hr=E_NOINTERFACE;
|
|
IServiceProvider *pSP;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::QueryService");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Currently the msgtable doesn't expose any objects, but will delegate to the folder to see if it can handle it
|
|
if (guidService == IID_IMessageFolder)
|
|
{
|
|
if (m_pFolder)
|
|
hr = m_pFolder->QueryInterface(riid, ppvObject);
|
|
}
|
|
else if (m_pFolder && m_pFolder->QueryInterface(IID_IServiceProvider, (LPVOID *)&pSP) == S_OK)
|
|
{
|
|
// Query Service This
|
|
hr = pSP->QueryService(guidService, riid, ppvObject);
|
|
|
|
// Release It
|
|
pSP->Release();
|
|
}
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::FindNextRow
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::FindNextRow(ROWINDEX iStartRow, LPCTSTR pszFindString,
|
|
FINDNEXTFLAGS dwFlags, BOOL fIncludeBody, ROWINDEX *piNextRow, BOOL *pfWrapped)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPMESSAGEINFO pMessage=NULL;
|
|
ROWINDEX iCurrent;
|
|
DWORD cchFindString;
|
|
BOOL fWrapAround=FALSE;
|
|
HLOCK hLock=NULL;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::QueryService");
|
|
|
|
// Invalid Args
|
|
Assert(pszFindString && piNextRow);
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Initialize
|
|
*piNextRow = -1;
|
|
if (pfWrapped)
|
|
*pfWrapped = FALSE;
|
|
|
|
// Get Prefix Length
|
|
cchFindString = lstrlen(pszFindString);
|
|
|
|
// Lock the Folder
|
|
IF_FAILEXIT(hr = m_pDB->Lock(&hLock));
|
|
|
|
// Set iCurrent
|
|
iCurrent = iStartRow >= m_cRows ? 0 : iStartRow;
|
|
|
|
// COLUMN_TO
|
|
if (FINDNEXT_TYPEAHEAD != dwFlags )
|
|
iCurrent++;
|
|
|
|
// Start my Loop
|
|
while (1)
|
|
{
|
|
// Start back at zero
|
|
if (iCurrent >= m_cRows)
|
|
{
|
|
// We Wrapped Around
|
|
fWrapAround = TRUE;
|
|
|
|
if (pfWrapped)
|
|
*pfWrapped = TRUE;
|
|
|
|
// Start back at zero
|
|
iCurrent = 0;
|
|
}
|
|
|
|
// Get the Row Info
|
|
IF_FAILEXIT(hr = GetRow(iCurrent, &pMessage));
|
|
|
|
// How to search...
|
|
if (FINDNEXT_ALLCOLUMNS == dwFlags)
|
|
{
|
|
// Display to
|
|
if (pMessage->pszDisplayTo && StrStrIA(pMessage->pszDisplayTo, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Email To
|
|
if (pMessage->pszEmailTo && StrStrIA(pMessage->pszEmailTo, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Display From
|
|
if (pMessage->pszDisplayFrom && StrStrIA(pMessage->pszDisplayFrom, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Email From
|
|
if (pMessage->pszEmailFrom && StrStrIA(pMessage->pszEmailFrom, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Subject
|
|
if (pMessage->pszNormalSubj && StrStrIA(pMessage->pszNormalSubj, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Folder
|
|
if (pMessage->pszFolder && StrStrIA(pMessage->pszFolder, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Account name
|
|
if (pMessage->pszAcctName && StrStrIA(pMessage->pszAcctName, pszFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
|
|
// Search the Body ?
|
|
if (fIncludeBody && pMessage->faStream)
|
|
{
|
|
// Locals
|
|
BOOL fMatch=FALSE;
|
|
IMimeMessage *pMessageObject;
|
|
IStream *pStream;
|
|
|
|
// Open the Stream
|
|
if (SUCCEEDED(m_pFolder->OpenMessage(pMessage->idMessage, OPEN_MESSAGE_CACHEDONLY, &pMessageObject, NOSTORECALLBACK)))
|
|
{
|
|
// Try to Get the Plain Text Stream
|
|
if (FAILED(pMessageObject->GetTextBody(TXT_PLAIN, IET_DECODED, &pStream, NULL)))
|
|
{
|
|
// Try to get the HTML stream
|
|
if (FAILED(pMessageObject->GetTextBody(TXT_HTML, IET_DECODED, &pStream, NULL)))
|
|
pStream = NULL;
|
|
}
|
|
|
|
// Do we have a strema
|
|
if (pStream)
|
|
{
|
|
// Search the Stream
|
|
fMatch = StreamSubStringMatch(pStream, (LPSTR)pszFindString);
|
|
|
|
// Release the Stream
|
|
pStream->Release();
|
|
}
|
|
|
|
// Cleanup
|
|
pMessageObject->Release();
|
|
}
|
|
|
|
// Found a Match ?
|
|
if (fMatch)
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Handle the column to search on...
|
|
switch(m_SortInfo.idColumn)
|
|
{
|
|
case COLUMN_TO:
|
|
if (pMessage->pszDisplayTo && 0 == StrCmpNI(pszFindString, pMessage->pszDisplayTo, cchFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case COLUMN_FROM:
|
|
if (pMessage->pszDisplayFrom && 0 == StrCmpNI(pszFindString, pMessage->pszDisplayFrom, cchFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case COLUMN_SUBJECT:
|
|
if (pMessage->pszNormalSubj && 0 == StrCmpNI(pszFindString, pMessage->pszNormalSubj, cchFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case COLUMN_FOLDER:
|
|
if (pMessage->pszFolder && 0 == StrCmpNI(pszFindString, pMessage->pszFolder, cchFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case COLUMN_ACCOUNT:
|
|
if (pMessage->pszAcctName && 0 == StrCmpNI(pszFindString, pMessage->pszAcctName, cchFindString))
|
|
{
|
|
*piNextRow = iCurrent;
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
SafeReleaseRow(this, pMessage);
|
|
|
|
// Increment iCurrent
|
|
iCurrent++;
|
|
|
|
// Wrapped and back to original row
|
|
if (fWrapAround && iCurrent >= iStartRow)
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeReleaseRow(this, pMessage);
|
|
|
|
// Unlock
|
|
m_pDB->Unlock(&hLock);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Collapse
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::Collapse(ROWINDEX iRow)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::Collapse");
|
|
|
|
// Call Internal Function
|
|
return(_CollapseThread(iRow, TRUE));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_CollapseThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_CollapseThread(ROWINDEX iRow, BOOL fNotify)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ROWINDEX iParent;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_CollapseThread");
|
|
|
|
// Expand All ?
|
|
if (INVALID_ROWINDEX == iRow)
|
|
{
|
|
// Walk through the Roots in the View...
|
|
for (iRow = 0; iRow < m_cView; iRow++)
|
|
{
|
|
// Set pRow
|
|
if (NULL == m_prgpView[iRow]->pParent)
|
|
{
|
|
// Set iParent
|
|
iParent = iRow;
|
|
|
|
// _CollapseSingleThread
|
|
IF_FAILEXIT(hr = _CollapseSingleThread(&iRow, m_prgpView[iRow], fNotify));
|
|
|
|
// Notify ?
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iParent, iParent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, expand one row
|
|
else
|
|
{
|
|
// Get the Row
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// Set iParent
|
|
iParent = iRow;
|
|
|
|
// _ExpandSingleThread
|
|
IF_FAILEXIT(hr = _CollapseSingleThread(&iRow, pRow, fNotify));
|
|
|
|
// Notify ?
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iParent, iParent);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Flush
|
|
if (fNotify)
|
|
_FlushNotificationQueue(TRUE);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_CollapseSingleThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_CollapseSingleThread(LPROWINDEX piCurrent,
|
|
LPROWINFO pParent, BOOL fNotify)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pCurrent;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_CollapseSingleThread");
|
|
|
|
// Mark Parent as Expanded...
|
|
pParent->fExpanded = FALSE;
|
|
|
|
// Set row state
|
|
pParent->dwState = 0;
|
|
|
|
// If no children
|
|
if (NULL == pParent->pChild)
|
|
return(S_OK);
|
|
|
|
// Loop through the children...
|
|
for (pCurrent = pParent->pChild; pCurrent != NULL; pCurrent = pCurrent->pSibling)
|
|
{
|
|
// If not visible
|
|
if (pCurrent->fVisible)
|
|
{
|
|
// Increment
|
|
(*piCurrent)++;
|
|
|
|
// Validate
|
|
Assert(m_prgpView[(*piCurrent)] == pCurrent);
|
|
|
|
// Insert pCurrent's Children
|
|
IF_FAILEXIT(hr = _CollapseSingleThread(piCurrent, pCurrent, fNotify));
|
|
|
|
// Insert into View
|
|
_DeleteFromView((*piCurrent), pCurrent);
|
|
|
|
// Insert the Row
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_DELETE, *piCurrent, INVALID_ROWINDEX, TRUE);
|
|
}
|
|
|
|
// Decrement
|
|
(*piCurrent)--;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::Expand
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::Expand(ROWINDEX iRow)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::Collapse");
|
|
|
|
// Call Internal Function
|
|
return(_ExpandThread(iRow, TRUE, FALSE));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_ExpandThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_ExpandThread(ROWINDEX iRow, BOOL fNotify, BOOL fReExpand)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ROWINDEX iParent;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_ExpandThread");
|
|
|
|
// Expand All ?
|
|
if (INVALID_ROWINDEX == iRow)
|
|
{
|
|
// Walk through the Roots in the View...
|
|
for (iRow = 0; iRow < m_cView; iRow++)
|
|
{
|
|
// Set pRow
|
|
if (NULL == m_prgpView[iRow]->pParent)
|
|
{
|
|
// Set iParent
|
|
iParent = iRow;
|
|
|
|
// _ExpandSingleThread
|
|
IF_FAILEXIT(hr = _ExpandSingleThread(&iRow, m_prgpView[iRow], fNotify, fReExpand));
|
|
|
|
// Notify ?
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iParent, iParent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise, expand one row
|
|
else
|
|
{
|
|
// Get the Row
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRow, &pRow));
|
|
|
|
// Set iParent
|
|
iParent = iRow;
|
|
|
|
// _ExpandSingleThread
|
|
IF_FAILEXIT(hr = _ExpandSingleThread(&iRow, pRow, fNotify, fReExpand));
|
|
|
|
// Notify ?
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iParent, iParent);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Flush
|
|
if (fNotify)
|
|
_FlushNotificationQueue(TRUE);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_ExpandSingleThread
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_ExpandSingleThread(LPROWINDEX piCurrent,
|
|
LPROWINFO pParent, BOOL fNotify, BOOL fReExpand)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pCurrent;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_ExpandSingleThread");
|
|
|
|
// If not delayed inserted...
|
|
if (fReExpand && FALSE == pParent->fExpanded)
|
|
return(S_OK);
|
|
|
|
// Mark Parent as Expanded...
|
|
pParent->fExpanded = TRUE;
|
|
|
|
// Set row state
|
|
pParent->dwState = 0;
|
|
|
|
// If no children
|
|
if (NULL == pParent->pChild)
|
|
return(S_OK);
|
|
|
|
// Loop through the children...
|
|
for (pCurrent = pParent->pChild; pCurrent != NULL; pCurrent = pCurrent->pSibling)
|
|
{
|
|
// Increment piCurrent
|
|
(*piCurrent)++;
|
|
|
|
// If not visible
|
|
if (FALSE == pCurrent->fVisible)
|
|
{
|
|
// Insert into View
|
|
_InsertIntoView((*piCurrent), pCurrent);
|
|
|
|
// Insert the Row
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_INSERT, *piCurrent, INVALID_ROWINDEX, TRUE);
|
|
}
|
|
}
|
|
|
|
// Otherwise, valident entry in view index
|
|
else
|
|
Assert(m_prgpView[(*piCurrent)] == pCurrent);
|
|
|
|
// Insert pCurrent's Children
|
|
IF_FAILEXIT(hr = _ExpandSingleThread(piCurrent, pCurrent, fNotify, fReExpand));
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_DeleteFromView
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_DeleteFromView(ROWINDEX iRow, LPROWINFO pRow)
|
|
{
|
|
// Better not be visible yet
|
|
Assert(TRUE == pRow->fVisible);
|
|
|
|
// Correct Row
|
|
Assert(m_prgpView[iRow] == pRow);
|
|
|
|
// Visible...
|
|
pRow->fVisible = FALSE;
|
|
|
|
// Collapse the Array
|
|
MoveMemory(&m_prgpView[iRow], &m_prgpView[iRow + 1], sizeof(LPROWINFO) * (m_cView - (iRow + 1)));
|
|
|
|
// Decrement m_cView
|
|
m_cView--;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_InsertIntoView
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_InsertIntoView(ROWINDEX iRow, LPROWINFO pRow)
|
|
{
|
|
// Better not be visible yet
|
|
Assert(FALSE == pRow->fVisible);
|
|
|
|
// Visible...
|
|
pRow->fVisible = TRUE;
|
|
|
|
// Increment view Count
|
|
m_cView++;
|
|
|
|
// Shift The Array
|
|
MoveMemory(&m_prgpView[iRow + 1], &m_prgpView[iRow], sizeof(LPROWINFO) * (m_cView - iRow));
|
|
|
|
// Set the Index
|
|
m_prgpView[iRow] = pRow;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_RowTableInsert
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_RowTableInsert(ROWORDINAL iOrdinal, LPMESSAGEINFO pMessage)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD i;
|
|
LPROWINFO pRow;
|
|
ROWINDEX iRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_RowTableInsert");
|
|
|
|
// Failure
|
|
if (iOrdinal >= m_cRows + 1)
|
|
{
|
|
Assert(FALSE);
|
|
return(TraceResult(E_FAIL));
|
|
}
|
|
|
|
// Do I need to grow the table
|
|
if (m_cRows + 1 >= m_cAllocated)
|
|
{
|
|
// Realloc
|
|
IF_FAILEXIT(hr = HrRealloc((LPVOID *)&m_prgpRow, sizeof(LPROWINFO) * (m_cRows + CGROWTABLE)));
|
|
|
|
// Realloc
|
|
IF_FAILEXIT(hr = HrRealloc((LPVOID *)&m_prgpView, sizeof(LPROWINFO) * (m_cRows + CGROWTABLE)));
|
|
|
|
// Set m_cAllocated
|
|
m_cAllocated = m_cRows + CGROWTABLE;
|
|
}
|
|
|
|
// Create a Row
|
|
IF_FAILEXIT(hr = _CreateRow(pMessage, &pRow));
|
|
|
|
// Don't Free
|
|
pMessage->pAllocated = NULL;
|
|
|
|
// Increment Row Count
|
|
m_cRows++;
|
|
|
|
// Shift The Array
|
|
MoveMemory(&m_prgpRow[iOrdinal + 1], &m_prgpRow[iOrdinal], sizeof(LPROWINFO) * (m_cRows - iOrdinal));
|
|
|
|
// Set pRow
|
|
m_prgpRow[iOrdinal] = pRow;
|
|
|
|
// If the row is Filtered, then just return
|
|
pRow->fFiltered = _FIsFiltered(pRow);
|
|
|
|
// Get Hidden Bit
|
|
pRow->fHidden = _FIsHidden(pRow);
|
|
|
|
// If not filtered and not hidden
|
|
if (pRow->fFiltered || pRow->fHidden)
|
|
{
|
|
// Update Filtered Count
|
|
m_cFiltered++;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// If this is a news folder, then lets just wait for a while...we will get hit with a force sort later...
|
|
if (TRUE == m_fSynching && FOLDER_NEWS == m_Folder.tyFolder)
|
|
{
|
|
// Set Expanded
|
|
pRow->fExpanded = m_SortInfo.fExpandAll;
|
|
|
|
// Set fDelayed
|
|
pRow->fDelayed = TRUE;
|
|
|
|
// Count Skiped
|
|
m_cDelayed++;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// If not filtered
|
|
_AdjustUnreadCount(pRow, 1);
|
|
|
|
// Show the Row
|
|
_ShowRow(pRow);
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_ShowRow
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_ShowRow(LPROWINFO pRow)
|
|
{
|
|
// Locals
|
|
ROWINDEX iRow = INVALID_ROWINDEX;
|
|
|
|
// Compare
|
|
if (m_SortInfo.fShowReplies)
|
|
{
|
|
// Have Addresses
|
|
if (pRow->Message.pszEmailFrom && m_pszEmail)
|
|
{
|
|
// From Me
|
|
if (0 == lstrcmpi(m_pszEmail, pRow->Message.pszEmailFrom))
|
|
{
|
|
// Set the Highlight
|
|
pRow->Message.wHighlight = m_clrWatched;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Threaded ?
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Insert Message Id into the hash table
|
|
if (pRow->Message.pszMessageId)
|
|
{
|
|
// Insert It
|
|
m_pThreadMsgId->Insert(pRow->Message.pszMessageId, (LPVOID)pRow, HF_NO_DUPLICATES);
|
|
}
|
|
|
|
// Insert this row into a thread...
|
|
if (S_OK == _InsertRowIntoThread(pRow, TRUE))
|
|
return(S_OK);
|
|
|
|
// Subject Threading ?
|
|
// [PaulHi] 6/22/99 Raid 81081
|
|
// Make sure we have a non-NULL subject string pointer before trying to hash it.
|
|
if (m_pThreadSubject && pRow->Message.pszNormalSubj)
|
|
{
|
|
// Insert Subject into Hash Table...
|
|
m_pThreadSubject->Insert(pRow->Message.pszNormalSubj, (LPVOID)pRow, HF_NO_DUPLICATES);
|
|
}
|
|
}
|
|
|
|
// If no parent, then just insert sorted into the view
|
|
Assert(NULL == pRow->pParent);
|
|
|
|
// Insert into View
|
|
for (iRow=0; iRow<m_cView; iRow++)
|
|
{
|
|
// Only Compare Against Roots
|
|
if (NULL == m_prgpView[iRow]->pParent)
|
|
{
|
|
// Insert Here...
|
|
if (_CompareMessages(&pRow->Message, &m_prgpView[iRow]->Message) <= 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Insert into the view
|
|
_InsertIntoView(iRow, pRow);
|
|
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_INSERT, iRow, INVALID_ROWINDEX);
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_GetRowFromOrdinal
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_GetRowFromOrdinal(ROWORDINAL iOrdinal,
|
|
LPMESSAGEINFO pExpected, LPROWINFO *ppRow)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_GetRowFromOrdinal");
|
|
|
|
// Failure
|
|
if (iOrdinal >= m_cRows)
|
|
{
|
|
Assert(FALSE);
|
|
return(TraceResult(E_FAIL));
|
|
}
|
|
|
|
// Set pRow
|
|
(*ppRow) = m_prgpRow[iOrdinal];
|
|
|
|
// Valid Row
|
|
if ((*ppRow)->Message.idMessage != pExpected->idMessage)
|
|
{
|
|
Assert(FALSE);
|
|
return(TraceResult(E_FAIL));
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_RowTableDelete
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_RowTableDelete(ROWORDINAL iOrdinal, LPMESSAGEINFO pMessage)
|
|
{
|
|
// Set pRow
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_RowTableDelete");
|
|
|
|
// Get Row From Ordinal
|
|
IF_FAILEXIT(hr = _GetRowFromOrdinal(iOrdinal, pMessage, &pRow));
|
|
|
|
// Shift The Array
|
|
MoveMemory(&m_prgpRow[iOrdinal], &m_prgpRow[iOrdinal + 1], sizeof(LPROWINFO) * (m_cRows - (iOrdinal + 1)));
|
|
|
|
// Decrement row Count
|
|
m_cRows--;
|
|
|
|
// If the message was filtered
|
|
if (pRow->fFiltered || pRow->fHidden)
|
|
{
|
|
// One less filtered item
|
|
m_cFiltered--;
|
|
}
|
|
|
|
// If not filtered
|
|
_AdjustUnreadCount(pRow, -1);
|
|
|
|
// Call Utility
|
|
_HideRow(pRow, TRUE);
|
|
|
|
// Release the Row
|
|
ReleaseRow(&pRow->Message);
|
|
|
|
exit:
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_HideRow
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_HideRow(LPROWINFO pRow, BOOL fNotify)
|
|
{
|
|
// Locals
|
|
LPROWINFO pReplace=NULL;
|
|
ROWINDEX iRow;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_HideRow");
|
|
|
|
// Threaded
|
|
if (m_SortInfo.fThreaded)
|
|
{
|
|
// Save First Child
|
|
pReplace = pRow->pChild;
|
|
}
|
|
|
|
// Delete the row from the thread
|
|
_DeleteRowFromThread(pRow, fNotify);
|
|
|
|
// Locate pRow in m_prgpView
|
|
if (FALSE == pRow->fVisible)
|
|
return(S_OK);
|
|
|
|
// Better not be hidden or filtered
|
|
Assert(FALSE == pRow->fHidden && FALSE == pRow->fFiltered);
|
|
|
|
// Must Succeed
|
|
SideAssert(SUCCEEDED(GetRowIndex(pRow->Message.idMessage, &iRow)));
|
|
|
|
// Replace ?
|
|
if (pReplace && TRUE == pRow->fVisible && FALSE == pReplace->fVisible)
|
|
{
|
|
// Validate
|
|
Assert(m_prgpView[iRow] == pRow);
|
|
|
|
// Insert into View
|
|
m_prgpView[iRow] = pReplace;
|
|
|
|
// Visible...
|
|
pReplace->fVisible = TRUE;
|
|
|
|
// Insert the Row
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iRow, iRow, TRUE);
|
|
}
|
|
}
|
|
|
|
// Otherwise, just delete it...
|
|
else
|
|
{
|
|
// Delete from view
|
|
_DeleteFromView(iRow, pRow);
|
|
|
|
// Notify ?
|
|
if (fNotify)
|
|
{
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_DELETE, iRow, INVALID_ROWINDEX);
|
|
}
|
|
}
|
|
|
|
// Not Visible
|
|
pRow->fVisible = FALSE;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_RowTableUpdate
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_RowTableUpdate(ROWORDINAL iOrdinal, LPMESSAGEINFO pMessage)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPROWINFO pRow;
|
|
ROWINDEX iMin;
|
|
ROWINDEX iMax;
|
|
BOOL fDone=FALSE;
|
|
BOOL fHidden;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_RowTableUpdate");
|
|
|
|
// Get Row From Ordinal
|
|
IF_FAILEXIT(hr = _GetRowFromOrdinal(iOrdinal, pMessage, &pRow));
|
|
|
|
// If not filtered
|
|
_AdjustUnreadCount(pRow, -1);
|
|
|
|
// Free pRow->Message
|
|
m_pDB->FreeRecord(&pRow->Message);
|
|
|
|
// Copy the Message Info
|
|
CopyMemory(&pRow->Message, pMessage, sizeof(MESSAGEINFO));
|
|
|
|
// Set dwReserved
|
|
pRow->Message.dwReserved = (DWORD_PTR)pRow;
|
|
|
|
// Don't Free
|
|
pMessage->pAllocated = NULL;
|
|
|
|
// Save the Highlight
|
|
pRow->wHighlight = pRow->Message.wHighlight;
|
|
|
|
// Clear this rows state...
|
|
pRow->dwState = 0;
|
|
|
|
// Compare
|
|
if (m_SortInfo.fShowReplies)
|
|
{
|
|
// Have Addresses
|
|
if (pRow->Message.pszEmailFrom && m_pszEmail)
|
|
{
|
|
// From Me
|
|
if (0 == lstrcmpi(m_pszEmail, pRow->Message.pszEmailFrom))
|
|
{
|
|
// Set the Highlight
|
|
pRow->Message.wHighlight = m_clrWatched;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hidden
|
|
fHidden = _FIsHidden(pRow);
|
|
|
|
// If the message was filtered, but isn't filtered now...
|
|
if (TRUE == pRow->fFiltered)
|
|
{
|
|
// Reset the Filtered Bit
|
|
if (FALSE == _FIsFiltered(pRow))
|
|
{
|
|
// Set fFiltered
|
|
pRow->fFiltered = FALSE;
|
|
|
|
// If Not Hidden
|
|
if (FALSE == pRow->fHidden)
|
|
{
|
|
// Need to do something so that it gets shown
|
|
pRow->fHidden = !fHidden;
|
|
|
|
// Decrement m_cFiltered
|
|
m_cFiltered--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not filtered
|
|
if (FALSE == pRow->fFiltered)
|
|
{
|
|
// Is it hidden now
|
|
if (FALSE == pRow->fHidden && TRUE == fHidden)
|
|
{
|
|
// If not filtered
|
|
_AdjustUnreadCount(pRow, -1);
|
|
|
|
// Hide the Row
|
|
_HideRow(pRow, TRUE);
|
|
|
|
// Its Hidden
|
|
pRow->fHidden = TRUE;
|
|
|
|
// Increment Filtered
|
|
m_cFiltered++;
|
|
|
|
// Done
|
|
fDone = TRUE;
|
|
}
|
|
|
|
// Otherwise, if it was hidden, and now its not...
|
|
else if (TRUE == pRow->fHidden && FALSE == fHidden)
|
|
{
|
|
// Its Hidden
|
|
pRow->fHidden = FALSE;
|
|
|
|
// If not filtered
|
|
_AdjustUnreadCount(pRow, 1);
|
|
|
|
// Increment Filtered
|
|
m_cFiltered--;
|
|
|
|
// Show the row
|
|
_ShowRow(pRow);
|
|
|
|
// Done
|
|
fDone = TRUE;
|
|
}
|
|
}
|
|
|
|
// If not hidden and not filtered
|
|
if (FALSE == fDone && FALSE == pRow->fHidden && FALSE == pRow->fFiltered)
|
|
{
|
|
// If not filtered
|
|
_AdjustUnreadCount(pRow, 1);
|
|
|
|
// If this row is visible, then I just need to update this row...
|
|
if (pRow->fVisible)
|
|
{
|
|
// Get the Row Index
|
|
SideAssert(SUCCEEDED(GetRowIndex(pRow->Message.idMessage, &iMin)));
|
|
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iMin, iMin);
|
|
}
|
|
|
|
// Otherwise, update the thread range
|
|
else
|
|
{
|
|
// Get the index range of this thread
|
|
_GetThreadIndexRange(pRow, TRUE, &iMin, &iMax);
|
|
|
|
// Queue It
|
|
_QueueNotification(TRANSACTION_UPDATE, iMin, iMax);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_FlushNotificationQueue
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_FlushNotificationQueue(BOOL fFinal)
|
|
{
|
|
// Nothing to Notify
|
|
if (NULL == m_pNotify)
|
|
return(S_OK);
|
|
|
|
// Have Delete or Inserted rows ?
|
|
if (m_Notify.cRows > 0)
|
|
{
|
|
// TRANSACTION_INSERT
|
|
if (TRANSACTION_INSERT == m_Notify.tyCurrent)
|
|
{
|
|
// Is this It ?
|
|
m_pNotify->OnInsertRows(m_Notify.cRows, m_Notify.prgiRow, m_Notify.fIsExpandCollapse);
|
|
}
|
|
|
|
// TRANSACTION_DELETE
|
|
else if (TRANSACTION_DELETE == m_Notify.tyCurrent)
|
|
{
|
|
// Is this It ?
|
|
m_pNotify->OnDeleteRows(m_Notify.cRows, m_Notify.prgiRow, m_Notify.fIsExpandCollapse);
|
|
}
|
|
}
|
|
|
|
// Have Updated Rows ?
|
|
if (m_Notify.cUpdate > 0)
|
|
{
|
|
// Is this It ?
|
|
m_pNotify->OnUpdateRows(m_Notify.iRowMin, m_Notify.iRowMax);
|
|
|
|
// Reset Update Range
|
|
m_Notify.cUpdate = 0;
|
|
m_Notify.iRowMin = 0xffffffff;
|
|
m_Notify.iRowMax = 0;
|
|
}
|
|
|
|
// Nothing to Notify About
|
|
m_Notify.cRows = 0;
|
|
|
|
// Final ?
|
|
m_Notify.fClean = fFinal;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_QueueNotification
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_QueueNotification(TRANSACTIONTYPE tyTransaction,
|
|
ROWINDEX iRowMin, ROWINDEX iRowMax, BOOL fIsExpandCollapse /*=FALSE*/)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_QueueNotification");
|
|
|
|
// Nothing to Notify
|
|
if (NULL == m_pNotify)
|
|
return(S_OK);
|
|
|
|
// Not Clearn
|
|
m_Notify.fClean = FALSE;
|
|
|
|
// If Update
|
|
if (TRANSACTION_UPDATE == tyTransaction)
|
|
{
|
|
// Min
|
|
if (iRowMin < m_Notify.iRowMin)
|
|
m_Notify.iRowMin = iRowMin;
|
|
|
|
// Max
|
|
if (iRowMax > m_Notify.iRowMax)
|
|
m_Notify.iRowMax = iRowMax;
|
|
|
|
// Count Notify
|
|
m_Notify.cUpdate++;
|
|
}
|
|
|
|
// Otherwise...
|
|
else
|
|
{
|
|
// Queue It
|
|
if (tyTransaction != m_Notify.tyCurrent || m_Notify.fIsExpandCollapse != fIsExpandCollapse)
|
|
{
|
|
// Flush
|
|
_FlushNotificationQueue(FALSE);
|
|
|
|
// Save the New Type
|
|
m_Notify.tyCurrent = tyTransaction;
|
|
|
|
// Count fIsExpandCollapse
|
|
m_Notify.fIsExpandCollapse = (BYTE) !!fIsExpandCollapse;
|
|
}
|
|
|
|
// Grow the Queue Size
|
|
if (m_Notify.cRows + 1 > m_Notify.cAllocated)
|
|
{
|
|
// Realloc
|
|
IF_FAILEXIT(hr = HrRealloc((LPVOID *)&m_Notify.prgiRow, (m_Notify.cAllocated + 256) * sizeof(ROWINDEX)));
|
|
|
|
// Set cAlloc
|
|
m_Notify.cAllocated = (m_Notify.cAllocated + 256);
|
|
}
|
|
|
|
// Append the iRow
|
|
m_Notify.prgiRow[m_Notify.cRows] = iRowMin;
|
|
|
|
// Increment Row count
|
|
m_Notify.cRows++;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::OnTransaction
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::OnTransaction(HTRANSACTION hTransaction,
|
|
DWORD_PTR dwCookie, IDatabase *pDB)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ORDINALLIST Ordinals;
|
|
INDEXORDINAL iIndex;
|
|
MESSAGEINFO Message1={0};
|
|
MESSAGEINFO Message2={0};
|
|
TRANSACTIONTYPE tyTransaction;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::OnTransaction");
|
|
|
|
// Should have final bit set
|
|
IxpAssert(m_Notify.fClean == TRUE);
|
|
|
|
// Loop Through Notifications
|
|
while (hTransaction)
|
|
{
|
|
// Get the Transaction Info
|
|
IF_FAILEXIT(hr = pDB->GetTransaction(&hTransaction, &tyTransaction, &Message1, &Message2, &iIndex, &Ordinals));
|
|
|
|
// Insert
|
|
if (TRANSACTION_INSERT == tyTransaction)
|
|
{
|
|
// Good Ordinal
|
|
Assert(INVALID_ROWORDINAL != Ordinals.rgiRecord1[IINDEX_PRIMARY] && Ordinals.rgiRecord1[IINDEX_PRIMARY] > 0);
|
|
|
|
// Insert Row Into Table
|
|
_RowTableInsert(Ordinals.rgiRecord1[IINDEX_PRIMARY] - 1, &Message1);
|
|
}
|
|
|
|
// Delete
|
|
else if (TRANSACTION_DELETE == tyTransaction)
|
|
{
|
|
// Good Ordinal
|
|
Assert(INVALID_ROWORDINAL != Ordinals.rgiRecord1[IINDEX_PRIMARY] && Ordinals.rgiRecord1[IINDEX_PRIMARY] > 0);
|
|
|
|
// Delete Row From Table
|
|
_RowTableDelete(Ordinals.rgiRecord1[IINDEX_PRIMARY] - 1, &Message1);
|
|
}
|
|
|
|
// Update
|
|
else if (TRANSACTION_UPDATE == tyTransaction)
|
|
{
|
|
// Deleted
|
|
Assert(INVALID_ROWORDINAL != Ordinals.rgiRecord1[IINDEX_PRIMARY] && INVALID_ROWORDINAL != Ordinals.rgiRecord2[IINDEX_PRIMARY] && Ordinals.rgiRecord1[IINDEX_PRIMARY] == Ordinals.rgiRecord2[IINDEX_PRIMARY] && Ordinals.rgiRecord1[IINDEX_PRIMARY] > 0 && Ordinals.rgiRecord2[IINDEX_PRIMARY] > 0);
|
|
|
|
// Delete Row From Table
|
|
_RowTableUpdate(Ordinals.rgiRecord1[IINDEX_PRIMARY] - 1, &Message2);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
pDB->FreeRecord(&Message1);
|
|
pDB->FreeRecord(&Message2);
|
|
|
|
// Flush the Queue
|
|
_FlushNotificationQueue(TRUE);
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkThreadGetSelectionState
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkThreadGetSelectionState(CMessageTable *pThis,
|
|
LPROWINFO pRow, DWORD_PTR dwCookie)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
FOLDERTYPE tyFolder;
|
|
LPGETSELECTIONSTATE pState = (LPGETSELECTIONSTATE)dwCookie;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkThreadGetSelectionState");
|
|
|
|
// Is Deletetable
|
|
if (ISFLAGSET(pState->dwMask, SELECTION_STATE_DELETABLE))
|
|
{
|
|
// Validate
|
|
Assert(pThis->m_pFindFolder);
|
|
|
|
// Get the Folder Type
|
|
IF_FAILEXIT(hr = pThis->m_pFindFolder->GetMessageFolderType(pRow->Message.idMessage, &tyFolder));
|
|
|
|
// Get the State
|
|
if (FOLDER_NEWS == tyFolder)
|
|
{
|
|
// Set the State
|
|
FLAGSET(pState->dwState, SELECTION_STATE_DELETABLE);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkThreadGetIdList
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkThreadGetIdList(CMessageTable *pThis,
|
|
LPROWINFO pRow, DWORD_PTR dwCookie)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPMESSAGEIDLIST pList=(LPMESSAGEIDLIST)dwCookie;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkThreadGetIdList");
|
|
|
|
// Grow the Id List
|
|
IF_FAILEXIT(hr = pThis->_GrowIdList(pList, 1));
|
|
|
|
// Insert Id
|
|
pList->prgidMsg[pList->cMsgs++] = pRow->Message.idMessage;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkThreadGetState
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkThreadGetState(CMessageTable *pThis,
|
|
LPROWINFO pRow, DWORD_PTR dwCookie)
|
|
{
|
|
// Locals
|
|
LPGETTHREADSTATE pGetState = (LPGETTHREADSTATE)dwCookie;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkThreadGetState");
|
|
|
|
// Children
|
|
pGetState->cChildren++;
|
|
|
|
// Is Unread
|
|
if (0 != (pRow->Message.dwFlags & pGetState->dwFlags))
|
|
pGetState->cHasFlags++;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkThreadClearState
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkThreadClearState(CMessageTable *pThis,
|
|
LPROWINFO pRow, DWORD_PTR dwCookie)
|
|
{
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkThreadClearState");
|
|
|
|
// Clear State
|
|
pRow->dwState = 0;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkThreadIsFromMe
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkThreadIsFromMe(CMessageTable *pThis,
|
|
LPROWINFO pRow, DWORD_PTR dwCookie)
|
|
{
|
|
// Locals
|
|
LPTHREADISFROMME pIsFromMe = (LPTHREADISFROMME)dwCookie;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkThreadIsFromMe");
|
|
|
|
// m_pszEmail or pszEmailFrom is null
|
|
if (NULL == pRow->Message.pszEmailFrom)
|
|
return(S_OK);
|
|
|
|
// Compare
|
|
if (pThis->m_pszEmail && 0 == lstrcmpi(pThis->m_pszEmail, pRow->Message.pszEmailFrom))
|
|
{
|
|
// This thread is from me
|
|
pIsFromMe->fResult = TRUE;
|
|
|
|
// Set the Row
|
|
pIsFromMe->pRow = pRow;
|
|
|
|
// Override the highlight
|
|
pRow->Message.wHighlight = pThis->m_clrWatched;
|
|
}
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_WalkThreadHide
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_WalkThreadHide(CMessageTable *pThis,
|
|
LPROWINFO pRow, DWORD_PTR dwCookie)
|
|
{
|
|
// Locals
|
|
LPTHREADHIDE pHide = (LPTHREADHIDE)dwCookie;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_WalkThreadHide");
|
|
|
|
// Hide this row
|
|
pThis->_HideRow(pRow, pHide->fNotify);
|
|
|
|
// If not filtered
|
|
pThis->_AdjustUnreadCount(pRow, -1);
|
|
|
|
// Mark Row as Filtered
|
|
pRow->fFiltered = TRUE;
|
|
|
|
// Increment m_cFiltered
|
|
pThis->m_cFiltered++;
|
|
|
|
// Done
|
|
return(S_OK);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::_GrowIdList
|
|
//--------------------------------------------------------------------------
|
|
HRESULT CMessageTable::_GrowIdList(LPMESSAGEIDLIST pList, DWORD cNeeded)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::_GrowIdList");
|
|
|
|
// Allocate
|
|
if (pList->cMsgs + cNeeded > pList->cAllocated)
|
|
{
|
|
// Compute cGrow
|
|
DWORD cGrow = max(32, cNeeded);
|
|
|
|
// Realloc
|
|
IF_FAILEXIT(hr = HrRealloc((LPVOID *)&pList->prgidMsg, sizeof(MESSAGEID) * (pList->cAllocated + cGrow)));
|
|
|
|
// Increment dwREserved
|
|
pList->cAllocated += cGrow;
|
|
}
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// EnumerateRefs
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT EnumerateRefs(LPCSTR pszReferences, DWORD_PTR dwCookie, PFNENUMREFS pfnEnumRefs)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD cchRefs;
|
|
LPSTR pszRefs;
|
|
LPSTR pszFree=NULL;
|
|
LPSTR pszT;
|
|
BOOL fDone=FALSE;
|
|
CHAR szBuffer[1024];
|
|
|
|
// Trace
|
|
TraceCall("EnumerateRefs");
|
|
|
|
// If the message has a references line
|
|
if (NULL == pszReferences || '\0' == *pszReferences)
|
|
return(S_OK);
|
|
|
|
// Get Length
|
|
cchRefs = lstrlen(pszReferences);
|
|
|
|
// Use Buffer ?
|
|
if (cchRefs + 1 <= ARRAYSIZE(szBuffer))
|
|
pszRefs = szBuffer;
|
|
|
|
// Otherwise, duplicate it
|
|
else
|
|
{
|
|
// Allocate Memory
|
|
IF_NULLEXIT(pszFree = (LPSTR)g_pMalloc->Alloc(cchRefs + 1));
|
|
|
|
// Set pszRefs
|
|
pszRefs = pszFree;
|
|
}
|
|
|
|
// Copy It
|
|
CopyMemory(pszRefs, pszReferences, cchRefs + 1);
|
|
|
|
// Set pszT
|
|
pszT = (LPSTR)(pszRefs + cchRefs - 1);
|
|
|
|
// Strip
|
|
while (pszT > pszRefs && *pszT != '>')
|
|
*pszT-- = '\0';
|
|
|
|
// We have have ids
|
|
while (pszT >= pszRefs)
|
|
{
|
|
// Start of message Id ?
|
|
if (*pszT == '<')
|
|
{
|
|
// Callback function
|
|
(*pfnEnumRefs)(pszT, dwCookie, &fDone);
|
|
|
|
// Done
|
|
if (fDone)
|
|
goto exit;
|
|
|
|
// Strip
|
|
while (pszT > pszRefs && *pszT != '>')
|
|
*pszT-- = '\0';
|
|
}
|
|
|
|
// Decrement
|
|
pszT--;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszFree);
|
|
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// CMessageTable::GetRelativeRow
|
|
//--------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTable::IsChild(ROWINDEX iRowParent, ROWINDEX iRowChild)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_FALSE;
|
|
LPROWINFO pRow;
|
|
LPROWINFO pRowParent;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::IsChild");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Failure
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRowChild, &pRow));
|
|
IF_FAILEXIT(hr = _GetRowFromIndex(iRowParent, &pRowParent));
|
|
|
|
// Loop through all the parents of the child row to see if we find the
|
|
// specified parent row.
|
|
while (pRow->pParent)
|
|
{
|
|
if (pRow->pParent == pRowParent)
|
|
{
|
|
hr = S_OK;
|
|
goto exit;
|
|
}
|
|
|
|
pRow = pRow->pParent;
|
|
}
|
|
hr = S_FALSE;
|
|
|
|
exit:
|
|
return (hr);
|
|
}
|
|
|
|
STDMETHODIMP CMessageTable::GetAdBarUrl(IStoreCallback *pCallback)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Trace
|
|
TraceCall("CMessageTable::GetAdBarUrl");
|
|
|
|
// Validate State
|
|
if (!IsInitialized(this))
|
|
return(TraceResult(E_UNEXPECTED));
|
|
|
|
// Tell the Folder to Synch
|
|
IF_FAILEXIT(hr = m_pFolder->GetAdBarUrl(pCallback));
|
|
|
|
exit:
|
|
return(hr);
|
|
|
|
}
|