#include "pch.h"
#include "stddef.h"
#pragma hdrstop


/*-----------------------------------------------------------------------------
/ Query thread bits
/----------------------------------------------------------------------------*/

//
// Page size used for paging the result sets (the LDAP server core is a sync process)
// therefore getting it to return smaller result blobs is better for us
//

#define PAGE_SIZE                   64
#define MAX_RESULT                  10000


//
// When the query is issued we always pull back at least ADsPath and objectClass
// as properites (these are required for the viewer to work).  Therefore these define
// the mapping of these values to the returned column set.
//

#define PROPERTYMAP_ADSPATH         0
#define PROPERTYMAP_OBJECTCLASS     1
#define PROPERTYMAP_USER            2

#define INDEX_TO_PROPERTY(i)        (i+PROPERTYMAP_USER)


//
// THREADDATA this is the state structure for the thread, it encapsulates
// the parameters and other junk required to the keep the thread alive.
//

typedef struct
{
    LPTHREADINITDATA ptid;    
    INT              cProperties;            // number of properties in aProperties
    LPWSTR*          aProperties;            // array of string pointers for "property names"
    INT              cColumns;               // number of columsn in view
    INT*             aColumnToPropertyMap;   // mapping from display column index to property name
} THREADDATA, * LPTHREADDATA;


//
// Helper macro to send messages for the fg view, including the reference
// count
// 

#define SEND_VIEW_MESSAGE(ptid, uMsg, lParam) \
        SendMessage(GetParent(ptid->hwndView), uMsg, (ptid)->dwReference, lParam)

//
// Function prototypes for the query thread engine.
//

HRESULT QueryThread_IssueQuery(LPTHREADDATA ptd);
HRESULT QueryThread_BuildPropertyList(LPTHREADDATA ptd);
VOID QueryThread_FreePropertyList(LPTHREADDATA ptd);


/*-----------------------------------------------------------------------------
/ Helper functions
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ QueryThread_GetFilter
/ ----------------------
/   Construct the LDAP filter we are going to use for this query.
/
/ In:
/   ppQuery -> receives the full filter
/   pBaseFilter -> filter string to use as base
/   fShowHidden = show hidden objects?
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/

VOID _GetFilter(LPWSTR pFilter, UINT* pcchFilterLen, LPWSTR pBaseFilter, BOOL fShowHidden)
{
    HRESULT hr;

    TraceEnter(TRACE_QUERYTHREAD, "_GetFilter");

    if (pFilter)
        *pFilter = TEXT('\0');

    PutStringElementW(pFilter, pcchFilterLen, L"(&");

    if (!fShowHidden)
        PutStringElementW(pFilter, pcchFilterLen, c_szShowInAdvancedViewOnly);

    PutStringElementW(pFilter, pcchFilterLen, pBaseFilter);
    PutStringElementW(pFilter, pcchFilterLen, L")");

    TraceLeave();
}

HRESULT QueryThread_GetFilter(LPWSTR* ppFilter, LPWSTR pBaseFilter, BOOL fShowHidden)
{
    HRESULT hr;
    UINT cchFilterLen = 0;

    TraceEnter(TRACE_QUERYTHREAD, "QueryThread_GetFilter");

     _GetFilter(NULL, &cchFilterLen, pBaseFilter, fShowHidden);

    hr = LocalAllocStringLenW(ppFilter, cchFilterLen);
    FailGracefully(hr, "Failed to allocate buffer for query string");
    
    _GetFilter(*ppFilter, NULL, pBaseFilter, fShowHidden);
    
    hr = S_OK;

exit_gracefully:
   
    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ Background query thread, this does the work of issuing the query and then
/ populating the view.
/----------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------
/ QueryThread
/ -----------
/   Thread function sits spinning its wheels waiting for a query to be
/   received from the outside world.  The main result viewer communicates
/   with this code by ThreadSendMessage.
/
/ In:
/   pThreadParams -> structure that defines out thread information
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
DWORD WINAPI QueryThread(LPVOID pThreadParams)
{
    HRESULT hresCoInit;
    MSG msg;
    LPTHREADINITDATA pThreadInitData = (LPTHREADINITDATA)pThreadParams;
    THREADDATA td;

    ZeroMemory(&td, SIZEOF(td));

    td.ptid = pThreadInitData;
    //td.cProperties = 0;
    //td.aProperties = NULL;
    //td.cColumns = 0;
    //td.aColumnToPropertyMap = NULL;
    
    hresCoInit = CoInitialize(NULL);
    FailGracefully(hresCoInit, "Failed to CoInitialize");

    GetActiveWindow();                                      // ensure we have amessage queue

    QueryThread_IssueQuery(&td);

    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        switch (msg.message)
        {
            case RVTM_STOPQUERY:
                TraceMsg("RVTM_STOPQUERY received - ignoring");
                break;

            case RVTM_REFRESH:
            {
                td.ptid->dwReference = (DWORD)msg.wParam;
                QueryThread_IssueQuery(&td);
                break;
            }
            
            case RVTM_SETCOLUMNTABLE:
            {
                if (td.ptid->hdsaColumns)
                    DSA_DestroyCallback(td.ptid->hdsaColumns, FreeColumnCB, NULL);

                td.ptid->dwReference = (DWORD)msg.wParam;
                td.ptid->hdsaColumns = (HDSA)msg.lParam;        

                QueryThread_FreePropertyList(&td);
                QueryThread_IssueQuery(&td);
                break;
            }
                                          
            default:
                break;
        }
    }

exit_gracefully:

    QueryThread_FreePropertyList(&td);
    QueryThread_FreeThreadInitData(&td.ptid);

    if (SUCCEEDED(hresCoInit))
        CoUninitialize();

    DllRelease();
    ExitThread(0);
    return 0;               // BOGUS: not never called
}


/*-----------------------------------------------------------------------------
/ QueryThread_FreeThreadInitData
/ ------------------------------
/   Release the THREADINITDATA structure that we are given when the thread
/   is created.
/
/ In:
/   pptid ->-> thread init data structure to be released 
/
/ Out:
/   -
/----------------------------------------------------------------------------*/
VOID QueryThread_FreeThreadInitData(LPTHREADINITDATA* pptid)
{
    LPTHREADINITDATA ptid = *pptid;

    TraceEnter(TRACE_QUERYTHREAD, "QueryThread_FreeThreadInitData");

    if (ptid)
    {
        LocalFreeStringW(&ptid->pQuery);
        LocalFreeStringW(&ptid->pScope);

        if (ptid->hdsaColumns)
            DSA_DestroyCallback(ptid->hdsaColumns, FreeColumnCB, NULL);

        LocalFreeStringW(&ptid->pServer);
        LocalFreeStringW(&ptid->pUserName);
        LocalFreeStringW(&ptid->pPassword);

        LocalFree((HLOCAL)ptid);
        *pptid = NULL;
    }

    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ QueryThread_CheckForStopQuery
/ -----------------------------
/   Peek the message queue looking for a stop query message, if we
/   can find one then we must bail out.
/
/ In:
/   ptd -> thread data structure
/
/ Out:
/   fStopQuery
/----------------------------------------------------------------------------*/
BOOL QueryThread_CheckForStopQuery(LPTHREADDATA ptd)
{
    BOOL fStopQuery = FALSE;
    MSG msg;

    TraceEnter(TRACE_QUERYTHREAD, "QueryThread_CheckForStopQuery");

    while (PeekMessage(&msg, NULL, RVTM_FIRST, RVTM_LAST, PM_REMOVE))
    {
        TraceMsg("Found a RVTM_ message in queue, stopping query");
        fStopQuery = TRUE;
    }

    TraceLeaveValue(fStopQuery);
}


/*-----------------------------------------------------------------------------
/ QueryThread_IssueQuery
/ ----------------------
/   Issue a query using the IDirectorySearch interface, this is a more performant
/   to the wire interface that issues the query directly.   The code binds to 
/   the scope object (the specified path) and then issues the LDAP query
/   pumping the results into the viewer as required.
/
/ In:
/   ptd -> thread information structurre
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT QueryThread_IssueQuery(LPTHREADDATA ptd)
{
    HRESULT hr;
    DWORD dwres;
    LPTHREADINITDATA ptid = ptd->ptid;
    LPWSTR pQuery = NULL;
    INT cItems = 0, iColumn;
    INT cMaxResult = MAX_RESULT;
    BOOL fStopQuery = FALSE;
    IDirectorySearch* pDsSearch = NULL;
    LPWSTR pszTempPath = NULL;
    IDsDisplaySpecifier *pdds = NULL;
    ADS_SEARCH_HANDLE hSearch = NULL;
    ADS_SEARCHPREF_INFO prefInfo[3];
    ADS_SEARCH_COLUMN column;
    HDPA hdpaResults = NULL;
    LPQUERYRESULT pResult = NULL;
    WCHAR szBuffer[2048];               // MAX_URL_LENGHT
    INT resid;
    LPWSTR pColumnData = NULL;
    HKEY hkPolicy = NULL;
    USES_CONVERSION;

    TraceEnter(TRACE_QUERYTHREAD, "QueryThread_IssueQuery");    

    // The foreground gave us a query so we are going to go and issue
    // it now, having done this we will then be able to stream the 
    // result blobs back to the caller. 

    hr = QueryThread_GetFilter(&pQuery, ptid->pQuery, ptid->fShowHidden);
    FailGracefully(hr, "Failed to build LDAP query from scope, parameters + filter");

    Trace(TEXT("Query is: %s"), W2T(pQuery));
    Trace(TEXT("Scope is: %s"), W2T(ptid->pScope));
    
    // Get the IDsDisplaySpecifier interface:

    hr = CoCreateInstance(CLSID_DsDisplaySpecifier, NULL, CLSCTX_INPROC_SERVER, IID_IDsDisplaySpecifier, (void **)&pdds);
    FailGracefully(hr, "Failed to get the IDsDisplaySpecifier object");

    hr = pdds->SetServer(ptid->pServer, ptid->pUserName, ptid->pPassword, DSSSF_DSAVAILABLE);
    FailGracefully(hr, "Failed to server information");

    // initialize the query engine, specifying the scope, and the search parameters

    hr = QueryThread_BuildPropertyList(ptd);
    FailGracefully(hr, "Failed to build property array to query for");

    hr = ADsOpenObject(ptid->pScope, ptid->pUserName, ptid->pPassword, ADS_SECURE_AUTHENTICATION,
                            IID_IDirectorySearch, (LPVOID*)&pDsSearch);

    FailGracefully(hr, "Failed to get the IDirectorySearch interface for the given scope");

    prefInfo[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;     // sub-tree search
    prefInfo[0].vValue.dwType = ADSTYPE_INTEGER;
    prefInfo[0].vValue.Integer = ADS_SCOPE_SUBTREE;

    prefInfo[1].dwSearchPref = ADS_SEARCHPREF_ASYNCHRONOUS;     // async
    prefInfo[1].vValue.dwType = ADSTYPE_BOOLEAN;
    prefInfo[1].vValue.Boolean = TRUE;

    prefInfo[2].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;         // paged results
    prefInfo[2].vValue.dwType = ADSTYPE_INTEGER;
    prefInfo[2].vValue.Integer = PAGE_SIZE;

    hr = pDsSearch->SetSearchPreference(prefInfo, ARRAYSIZE(prefInfo));
    FailGracefully(hr, "Failed to set search preferences");

    hr = pDsSearch->ExecuteSearch(pQuery, ptd->aProperties, ptd->cProperties, &hSearch);
    FailGracefully(hr, "Failed in ExecuteSearch");

    // pick up the policy value which defines the max results we are going to use

    dwres = RegOpenKey(HKEY_CURRENT_USER, DS_POLICY, &hkPolicy);
    if (ERROR_SUCCESS == dwres)
    {
        DWORD dwType, cbSize;

        dwres = RegQueryValueEx(hkPolicy, TEXT("QueryLimit"), NULL, &dwType, NULL, &cbSize);
        if ((ERROR_SUCCESS == dwres) && (dwType == REG_DWORD) && (cbSize == SIZEOF(cMaxResult)))
        {
            RegQueryValueEx(hkPolicy, TEXT("QueryLimit"), NULL, NULL, (LPBYTE)&cMaxResult, &cbSize);
        }

        RegCloseKey(hkPolicy);
    }
    

    // issue the query, pumping the results to the foreground UI which
    // will inturn populate the the list view

    Trace(TEXT("Result limit set to %d"), cMaxResult);

    for (cItems = 0 ; cItems < cMaxResult; cItems++)
    {
        ADsSetLastError(ERROR_SUCCESS, NULL, NULL);        // clear the ADSI previous errror

        hr = pDsSearch->GetNextRow(hSearch);

        fStopQuery = QueryThread_CheckForStopQuery(ptd);
        Trace(TEXT("fStopQuery %d, hr %08x"), fStopQuery, hr);

        if (fStopQuery || (hr == S_ADS_NOMORE_ROWS))
        {
            DWORD dwError;
            WCHAR wszError[64], wszName[64];

            hr = ADsGetLastError(&dwError, wszError, ARRAYSIZE(wszError), wszName, ARRAYSIZE(wszName));
            if (SUCCEEDED(hr) && (dwError != ERROR_MORE_DATA))
            {
                break;
            }

            hr = S_OK;                                      // we have more data so lets continue
        }

        FailGracefully(hr, "Failed in GetNextRow");

        // We have a result, lets ensure that we have posted the blob
        // we are building before we start constructing a new one.  We
        // send pages of items to the fg thread for it to add to the
        // result view, if the blob returns FALSE then we must tidy the
        // DPA before continuing.
        
        if (((cItems % 10) == 0) && hdpaResults)          // 10 is a nice block size
        {
            TraceMsg("Posting results blob to fg thread");
            
            if (!SEND_VIEW_MESSAGE(ptid, DSQVM_ADDRESULTS, (LPARAM)hdpaResults))
                DPA_DestroyCallback(hdpaResults, FreeQueryResultCB, IntToPtr(ptd->cColumns));

            hdpaResults = NULL;
        }

        if (!hdpaResults)
        {
            hdpaResults = DPA_Create(PAGE_SIZE);
            TraceAssert(hdpaResults);

            if (!hdpaResults)
                ExitGracefully(hr, E_OUTOFMEMORY, "Failed to allocate result DPA");
        }

        // Add the result we have to the result blob, the first
        // two things we need are the class and the ADsPath of the
        // object, then loop over the properties to generate the
        // column data

        pResult = (LPQUERYRESULT)LocalAlloc(LPTR, SIZEOF(QUERYRESULT)+(SIZEOF(COLUMNVALUE)*(ptd->cColumns-1)));
        TraceAssert(pResult);

        if (pResult)
        {
            // Get the ADsPath and ObjectClass of the object, these must remain UNICODE as
            // they are used later for binding to the object.  All other display information
            // can be fixed up later.

            pResult->iImage = -1;

            // get the ADsPath.  If the provider is GC: then replace with LDAP: so that
            // when we interact with this object we are kept happy.

            hr = pDsSearch->GetColumn(hSearch, c_szADsPath, &column);
            FailGracefully(hr, "Failed to get the ADsPath column");

            hr = StringFromSearchColumn(&column, &pResult->pPath);
            pDsSearch->FreeColumn(&column);

            Trace(TEXT("Object path: %s"), W2T(pResult->pPath));

            if (SUCCEEDED(hr) &&
                    ((pResult->pPath[0]== L'G') && (pResult->pPath[1] == L'C')))
            {
                TraceMsg("Replacing provider with LDAP:");

                hr = LocalAllocStringLenW(&pszTempPath, lstrlenW(pResult->pPath)+2);
                if (SUCCEEDED(hr))
                {
                    StrCpyW(pszTempPath, c_szLDAP);
                    StrCatW(pszTempPath, pResult->pPath+3);           // skip GC:

                    LocalFreeStringW(&pResult->pPath);
                    pResult->pPath = pszTempPath;
                }

                Trace(TEXT("New path is: %s"), W2T(pResult->pPath));
            }

            FailGracefully(hr, "Failed to get ADsPath from column");

            // get the objectClass

            hr = pDsSearch->GetColumn(hSearch, c_szObjectClass, &column);
            FailGracefully(hr, "Failed to get the objectClass column");

            hr = ObjectClassFromSearchColumn(&column, &pResult->pObjectClass);
            pDsSearch->FreeColumn(&column);
            FailGracefully(hr, "Failed to get object class from column");

            // Now ensure that we have the icon cache, and then walk the list of columns
            // getting the text that represents those.

            if (SUCCEEDED(pdds->GetIconLocation(pResult->pObjectClass, DSGIF_GETDEFAULTICON, szBuffer, ARRAYSIZE(szBuffer), &resid)))
            {
                pResult->iImage = Shell_GetCachedImageIndex(W2T(szBuffer), resid, 0x0);
                Trace(TEXT("Image index from shell is: %d"), pResult->iImage);
            }

            // is the class a container, mark this state into the result object

            pResult->fIsContainer = pdds->IsClassContainer(pResult->pObjectClass, pResult->pPath, 0x0);

            for (iColumn = 0 ; iColumn < ptd->cColumns ; iColumn++)
            {
                LPWSTR pProperty = ptd->aProperties[ptd->aColumnToPropertyMap[iColumn]];    
                TraceAssert(pProperty);

                pResult->aColumn[iColumn].iPropertyType = PROPERTY_ISUNDEFINED;     // empty column

                hr = pDsSearch->GetColumn(hSearch, pProperty, &column);
                if ((hr != E_ADS_COLUMN_NOT_SET) && FAILED(hr))
                {
                    Trace(TEXT("Failed to get column %d with code %08x"), iColumn, hr);
                    hr = E_ADS_COLUMN_NOT_SET;
                }

                if (hr != E_ADS_COLUMN_NOT_SET)
                {
                    LPCOLUMN pColumn = (LPCOLUMN)DSA_GetItemPtr(ptid->hdsaColumns, iColumn);
                    TraceAssert(pColumn);

                    switch (pColumn->iPropertyType)
                    {
                        case PROPERTY_ISUNKNOWN:
                        case PROPERTY_ISSTRING:
                        {                            
                            // we are treating the property as a string, therefore convert the search
                            // column to a string value and convert as required.

                            pResult->aColumn[iColumn].iPropertyType = PROPERTY_ISSTRING;

                            if (pColumn->fHasColumnHandler)
                            {
                                // we have the CLSID for a column handler, therefore lets CoCreate it
                                // and pass it to the ::GetText method.

                                if (!pColumn->pColumnHandler)
                                {
                                    TraceGUID("Attempting to create IDsQueryColumnHandler from GUID: ", pColumn->clsidColumnHandler);

                                    hr = CoCreateInstance(pColumn->clsidColumnHandler, NULL, CLSCTX_INPROC_SERVER, 
                                                                IID_IDsQueryColumnHandler, (LPVOID*)&pColumn->pColumnHandler);
                                    if (SUCCEEDED(hr))
                                        hr = pColumn->pColumnHandler->Initialize(0x0, ptid->pServer, ptid->pUserName, ptid->pPassword);

                                    if (FAILED(hr))
                                    {
                                        TraceMsg("Failed to CoCreate the column handler, marking the column as not having one");
                                        pColumn->fHasColumnHandler = FALSE;
                                        pColumn->pColumnHandler = NULL;
                                    }
                                }                                        

                                // if pColumnHandler != NULL, then call its ::GetText method, this is the string we should
                                // then place into the column.

                                if (pColumn->pColumnHandler)
                                {
                                    pColumn->pColumnHandler->GetText(&column, szBuffer, ARRAYSIZE(szBuffer));
                                    LocalAllocStringW2T(&pResult->aColumn[iColumn].pszText, szBuffer);
                                }
                            }
                            else
                            {
                                // if we were able to convert the column value to a string,
                                // then lets pass it to the column handler (if there is one
                                // to get the display string), or just copy this into the column
                                // structure (thunking accordingly).
                        
                                if (SUCCEEDED(StringFromSearchColumn(&column, &pColumnData)))
                                {
                                    LocalAllocStringW2T(&pResult->aColumn[iColumn].pszText, pColumnData);
                                    LocalFreeStringW(&pColumnData);
                                }
                            }

                            break;
                        }
                        
                        case PROPERTY_ISBOOL:                                   // treat the BOOL as a number
                        case PROPERTY_ISNUMBER:
                        {
                            // its a number, therefore lets pick up the number value from the
                            // result and store that.

                            pResult->aColumn[iColumn].iPropertyType = PROPERTY_ISNUMBER;
                            pResult->aColumn[iColumn].iValue = column.pADsValues->Integer;
                            break;
                        }
                    }

                    pDsSearch->FreeColumn(&column);
                }
            }        

            if (-1 == DPA_AppendPtr(hdpaResults, pResult))
            {
                FreeQueryResult(pResult, ptd->cColumns);
                LocalFree((HLOCAL)pResult);
            }

            pResult = NULL;
        }
    }

    hr = S_OK;

exit_gracefully:

    Trace(TEXT("cItems %d, (hdpaResults %08x (%d))"), cItems, hdpaResults, hdpaResults ? DPA_GetPtrCount(hdpaResults):0);

    if (hdpaResults)
    {
        // As we send bunches of results to the fg thread check to see if we have a 
        // DPA with any pending items in it, if we do then lets ensure we post that
        // off, if that succedes (the msg returns TRUE) then we are done, otherwise
        // hdpaResults needs to be free'd

        Trace(TEXT("Posting remaining results to fg thread (%d)"), DPA_GetPtrCount(hdpaResults));

        if (SEND_VIEW_MESSAGE(ptid, DSQVM_ADDRESULTS, (LPARAM)hdpaResults))
            hdpaResults = NULL;
    }

    if (!fStopQuery)
    {
        SEND_VIEW_MESSAGE(ptid, DSQVM_FINISHED, (cItems == MAX_RESULT));
    }

    if (pResult)
    {
        FreeQueryResult(pResult, ptd->cColumns);
        LocalFree((HLOCAL)pResult);
    }

    if (hSearch && pDsSearch)
    {
        pDsSearch->CloseSearchHandle(hSearch);
    }

    LocalFreeStringW(&pQuery);

    DoRelease(pDsSearch);
    DoRelease(pdds);

    QueryThread_FreePropertyList(ptd);               // its void when we issue a new query

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ QueryThread_BuildPropertyList
/ -----------------------------
/   Given the column DSA construct the property maps and the property
/   list we are going to query for.  Internaly we always query for
/   ADsPath and objectClass, so walk the columns and work out
/   how many extra properties above this we have, then build an
/   array of property names containing the unique properties.
/
/   We also construct an index table that maps from a column index
/   to a property name.
/
/ In:
/   ptd -> thread information structurre
/
/ Out:
/   HRESULT
/----------------------------------------------------------------------------*/
HRESULT QueryThread_BuildPropertyList(LPTHREADDATA ptd)
{
    HRESULT hr;
    LPTHREADINITDATA ptid = ptd->ptid;
    INT i, j;
    USES_CONVERSION;

    TraceEnter(TRACE_QUERYTHREAD, "QueryThread_BuildPropertyList");

    // Walk the list of columns and compute the properties that are unique to this
    // query and generate a table for them.  First count the property table
    // based on the columns DSA

    ptd->cProperties = PROPERTYMAP_USER;
    ptd->aProperties = NULL;
    ptd->cColumns = DSA_GetItemCount(ptid->hdsaColumns);
    ptd->aColumnToPropertyMap = NULL;

    for (i = 0 ; i < DSA_GetItemCount(ptid->hdsaColumns); i++)
    {
        LPCOLUMN pColumn = (LPCOLUMN)DSA_GetItemPtr(ptid->hdsaColumns, i);
        TraceAssert(pColumn);

        if (StrCmpW(pColumn->pProperty, c_szADsPath) &&
                 StrCmpW(pColumn->pProperty, c_szObjectClass))
        {
           ptd->cProperties++;
        }
    }
       
    Trace(TEXT("cProperties %d"), ptd->cProperties);
        
    ptd->aProperties = (LPWSTR*)LocalAlloc(LPTR, SIZEOF(LPWSTR)*ptd->cProperties);
    ptd->aColumnToPropertyMap = (INT*)LocalAlloc(LPTR, SIZEOF(INT)*ptd->cColumns);

    if (!ptd->aProperties || !ptd->aColumnToPropertyMap)
        ExitGracefully(hr, E_OUTOFMEMORY, "Failed to allocate property array / display array");
    
    ptd->aProperties[PROPERTYMAP_ADSPATH] = c_szADsPath;
    ptd->aProperties[PROPERTYMAP_OBJECTCLASS] = c_szObjectClass;

    for (j = PROPERTYMAP_USER, i = 0 ; i < ptd->cColumns; i++)
    {
        LPCOLUMN pColumn = (LPCOLUMN)DSA_GetItemPtr(ptid->hdsaColumns, i);
        TraceAssert(pColumn);

        if (!StrCmpW(pColumn->pProperty, c_szADsPath))
        {
           ptd->aColumnToPropertyMap[i] = PROPERTYMAP_ADSPATH;
        }
        else if (!StrCmpW(pColumn->pProperty, c_szObjectClass))
        {
           ptd->aColumnToPropertyMap[i] = PROPERTYMAP_OBJECTCLASS;
        }
        else
        {
            ptd->aProperties[j] = pColumn->pProperty;
            ptd->aColumnToPropertyMap[i] = j++;
        }

        Trace(TEXT("Property: %s"), ptd->aProperties[ptd->aColumnToPropertyMap[i]]);
    }

    hr = S_OK;

exit_gracefully:

    if (FAILED(hr))
        QueryThread_FreePropertyList(ptd);

    TraceLeaveResult(hr);
}


/*-----------------------------------------------------------------------------
/ QueryThread_FreePropertyList
/ ----------------------------
/   Release a previously allocated property list assocaited with the
/   given thread.
/
/ In:
/   ptd -> thread information structurre
/
/ Out:
/   VOID
/----------------------------------------------------------------------------*/
VOID QueryThread_FreePropertyList(LPTHREADDATA ptd)
{
    TraceEnter(TRACE_QUERYTHREAD, "QueryThread_FreePropertyList");

    if (ptd->aProperties)
        LocalFree((HLOCAL)ptd->aProperties);
    if (ptd->aColumnToPropertyMap)
        LocalFree((HLOCAL)ptd->aColumnToPropertyMap);

    ptd->cProperties = 0;    
    ptd->aProperties = NULL;
    ptd->cColumns = 0;
    ptd->aColumnToPropertyMap = NULL;
    
    TraceLeave();
}


/*-----------------------------------------------------------------------------
/ CQueryThreadCH
/ --------------
/   Query thread column handler, this is a generic one used to convert
/   properties based on the CLSID we are instantiated with.
/----------------------------------------------------------------------------*/

class CQueryThreadCH : public IDsQueryColumnHandler
{
    private:
        LONG _cRef;
        CLSID _clsid;
        IADsPathname *_padp;
        IDsDisplaySpecifier *_pdds;

        DWORD _dwFlags;
        LPWSTR _pszServer;
        LPWSTR _pszUserName;
        LPWSTR _pszPassword;

    public:
        CQueryThreadCH(REFCLSID rCLSID);
        ~CQueryThreadCH();

        // *** IUnknown ***
        STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObject);
        STDMETHOD_(ULONG, AddRef)();
        STDMETHOD_(ULONG, Release)();

        // *** IDsQueryColumnHandler ***
        STDMETHOD(Initialize)(THIS_ DWORD dwFlags, LPCWSTR pszServer, LPCWSTR pszUserName, LPCWSTR pszPassword);
        STDMETHOD(GetText)(ADS_SEARCH_COLUMN* psc, LPWSTR pszBuffer, INT cchBuffer);
};

//
// constructor
//

CQueryThreadCH::CQueryThreadCH(REFCLSID rCLSID) :
    _cRef(1),
    _padp(NULL),
    _pdds(NULL),
    _clsid(rCLSID),
    _dwFlags(0),
    _pszServer(NULL),
    _pszUserName(NULL),
    _pszPassword(NULL)
{
    TraceEnter(TRACE_QUERYTHREAD, "CQueryThreadCH::CQueryThreadCH");
    TraceGUID("CLSID of property: ", rCLSID);
    DllAddRef();
    TraceLeave();
}

CQueryThreadCH::~CQueryThreadCH()
{
    TraceEnter(TRACE_QUERYTHREAD, "CQueryThreadCH::~CQueryThreadCH");

    DoRelease(_padp);       // free the name cracker
    DoRelease(_pdds);

    LocalFreeStringW(&_pszServer);
    LocalFreeStringW(&_pszUserName);
    LocalFreeStringW(&_pszPassword);

    DllRelease();

    TraceLeave();
}


//
// Handler IUnknown interface
//

ULONG CQueryThreadCH::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

ULONG CQueryThreadCH::Release()
{
    if (InterlockedDecrement(&_cRef))
        return _cRef;

    delete this;
    return 0;
}

HRESULT CQueryThreadCH::QueryInterface(REFIID riid, void **ppv)
{
    static const QITAB qit[] = 
    {
        QITABENT(CQueryThreadCH, IDsQueryColumnHandler),   // IID_IDsQueryColumnHandler
        {0, 0 },
    };
    return QISearch(this, qit, riid, ppv);
}


//
// Handle creating an instance of IDsFolderProperties for talking to WAB
//

STDAPI CQueryThreadCH_CreateInstance(IUnknown* punkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    CQueryThreadCH *pqtch = new CQueryThreadCH(*poi->pclsid);
    if (!pqtch)
        return E_OUTOFMEMORY;

    HRESULT hr = pqtch->QueryInterface(IID_IUnknown, (void **)ppunk);
    pqtch->Release();
    return hr;
}


/*-----------------------------------------------------------------------------
/ IDsQueryColumnHandler
/----------------------------------------------------------------------------*/

STDMETHODIMP CQueryThreadCH::Initialize(THIS_ DWORD dwFlags, LPCWSTR pszServer, LPCWSTR pszUserName, LPCWSTR pszPassword)
{
    TraceEnter(TRACE_QUERYTHREAD, "CQueryThread::Initialize");

    LocalFreeStringW(&_pszServer);
    LocalFreeStringW(&_pszUserName);
    LocalFreeStringW(&_pszPassword);

    // copy new parameters away

    _dwFlags = dwFlags;

    HRESULT hr = LocalAllocStringW(&_pszServer, pszServer);    
    if (SUCCEEDED(hr))
        hr = LocalAllocStringW(&_pszUserName, pszUserName);
        if (SUCCEEDED(hr))
        hr = LocalAllocStringW(&_pszPassword, pszPassword);

    DoRelease(_pdds)                                // discard previous IDisplaySpecifier object

    TraceLeaveResult(hr);
}

STDMETHODIMP CQueryThreadCH::GetText(ADS_SEARCH_COLUMN* psc, LPWSTR pszBuffer, INT cchBuffer)
{
    HRESULT hr;
    LPWSTR pValue = NULL;
    USES_CONVERSION;

    TraceEnter(TRACE_QUERYTHREAD, "CQueryThreadCH::GetText");

    if (!psc || !pszBuffer)
        ExitGracefully(hr, E_UNEXPECTED, "Bad parameters passed to handler");

    pszBuffer[0] = L'\0'; 

    if (IsEqualCLSID(_clsid, CLSID_PublishedAtCH) || IsEqualCLSID(_clsid, CLSID_MachineOwnerCH))
    {
        BOOL fPrefix = IsEqualCLSID(_clsid, CLSID_PublishedAtCH);
        LPCWSTR pszPath = psc->pADsValues[0].DNString;
        TraceAssert(pszPath != NULL);

        // convert the ADsPath into its canonical form which is easier for the user
        // to understand, CoCreate IADsPathname now instead of each time we call
        // PrettyifyADsPathname.

        if (!_padp)
        {
            hr = CoCreateInstance(CLSID_Pathname, NULL, CLSCTX_INPROC_SERVER, IID_IADsPathname, (LPVOID*)&_padp);
            FailGracefully(hr, "Failed to get IADsPathname interface");
        }

        if (FAILED(GetDisplayNameFromADsPath(pszPath, pszBuffer, cchBuffer, _padp, fPrefix)))
        {
            TraceMsg("Failed to get display name from path");
            StrCpyNW(pszBuffer, pszPath, cchBuffer);
        }
                                                                    
        hr = S_OK;
    }
    else if (IsEqualCLSID(_clsid, CLSID_ObjectClassCH))
    {        
        // get a string from the search column, and then look up the friendly name of the
        // class from its display specifier

        hr = ObjectClassFromSearchColumn(psc, &pValue);
        FailGracefully(hr, "Failed to get object class from psc");

        if (!_pdds)
        {
            DWORD dwFlags = 0;

            hr = CoCreateInstance(CLSID_DsDisplaySpecifier, NULL, CLSCTX_INPROC_SERVER, IID_IDsDisplaySpecifier, (void **)&_pdds);
            FailGracefully(hr, "Failed to get IDsDisplaySpecifier interface");

            hr = _pdds->SetServer(_pszServer, _pszUserName, _pszPassword, DSSSF_DSAVAILABLE);
            FailGracefully(hr, "Failed when setting server for display specifier object");                
        }

        _pdds->GetFriendlyClassName(pValue, pszBuffer, cchBuffer);
    }
    else if (IsEqualCLSID(_clsid, CLSID_MachineOwnerCH))
    {
        // convert the DN of the user object into a string that we can display
    }
    else if (IsEqualCLSID(_clsid, CLSID_MachineRoleCH))
    {
        // convert the userAccountControl value into something we can display for the user

        if (psc->dwADsType == ADSTYPE_INTEGER)
        {
            INT iType = psc->pADsValues->Integer;           // pick out the type

            if ((iType >= 4096) && (iType <= 8191))
            {
                TraceMsg("Returning WKS/SRV string");
                LoadStringW(GLOBAL_HINSTANCE, IDS_WKSORSERVER, pszBuffer, cchBuffer);
            }
            else if (iType >= 8192)
            {
                TraceMsg("Returning DC string");
                LoadStringW(GLOBAL_HINSTANCE, IDS_DC, pszBuffer, cchBuffer);
            }
            else
            {
                Trace(TEXT("Unknown type %x"), iType);
            }
        }
    }
    else
    {
        ExitGracefully(hr, E_UNEXPECTED, "m_clsid specifies column type not supported");
    }

    hr = S_OK;

exit_gracefully:

    LocalFreeStringW(&pValue);

    TraceLeaveResult(hr);
}
