//+-----------------------------------------------------------------------
//
//  Add/Remove Programs Data Source Object
//
//------------------------------------------------------------------------


#include "priv.h"

// Do not build this file if on Win9X or NT4
#ifndef DOWNLEVEL_PLATFORM

#include "datasrc.h"
#include "dump.h"
#include "util.h"

//---------------------------------------------------------------------------
//   
//---------------------------------------------------------------------------


// constructor
CDataSrc::CDataSrc()
{
    TraceMsg(TF_OBJLIFE, "(Mtx) creating");
    TraceAddRef(CDataSrc, _cRef);
    
    ASSERT(NULL == _parpevt);
    ASSERT(NULL == _pmtxarray);
    ASSERT(NULL == _psam);
    ASSERT(FALSE == _fAppsEnumed);
    ASSERT(FALSE == _fInEnumOp);
    
    _loadstate = LS_NOTSTARTED;
}


// destructor
CDataSrc::~CDataSrc()
{
    TraceMsg(TF_OBJLIFE, "(Mtx) destroying");

    ATOMICRELEASE(_pmtxarray);
    ATOMICRELEASE(_parpevt);
    ATOMICRELEASE(_psam);
}


/*--------------------------------------------------------------------
Purpose: IUnknown::QueryInterface
*/
STDMETHODIMP CDataSrc::QueryInterface(REFIID riid, LPVOID * ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CDataSrc, IARPSimpleProvider),
        QITABENT(CDataSrc, OLEDBSimpleProvider),
        QITABENT(CDataSrc, ISequentialStream),
        QITABENT(CDataSrc, IWorkerEvent),
        { 0 },
    };

    HRESULT hres = QISearch(this, (LPCQITAB)qit, riid, ppvObj);
    if (FAILED(hres))
        hres = CWorkerThread::QueryInterface(riid, ppvObj);

    return hres;
}


/*--------------------------------------------------------------------
Purpose: IARPWorker::KillWT
         Kills the worker thread that enumerates apps
*/
STDMETHODIMP CDataSrc::KillWT()
{
    // Primary thread wants to kill us, this means we are about to be released
    // also kill the mtxarray thread here, because that kill has to be on the main thread, too.
    // And we can't depend on CDataSrc descrutor to do it (because that final release could be called on the
    // back groud thread) 
    _KillMtxWorkerThread();

    return CWorkerThread::KillWT();
}

/*-------------------------------------------------------------------------
Purpose: IWorkerEvent::FireOnDataReady

         Called by worker thread when some data is ready.
*/
STDMETHODIMP 
CDataSrc::FireOnDataReady(
    DBROWCOUNT iRow
    )
{
    // OSP listener expects row to be 1-based
    _parpevt->RowChanged(iRow + 1);
    return S_OK;
}


/*-------------------------------------------------------------------------
Purpose: IWorkerEvent::FireOnFinished

         Called by worker thread when it is complete.
*/
STDMETHODIMP 
CDataSrc::FireOnFinished(void)
{
    _loadstate = LS_DONE;
    return S_OK;
}


/*-------------------------------------------------------------------------
Purpose: IWorkerEvent::FireOnDatasetChanged

         Called by worker thread when it is complete.
*/
STDMETHODIMP 
CDataSrc::FireOnDatasetChanged(void)
{
    if (_parpevt)
        _parpevt->DataSetChanged();
    return S_OK;
}

//  CDataSrc::_CalcRows
//      Calculate the number of rows in the OSP

DBROWCOUNT CDataSrc::_CalcRows(void)
{
    DBROWCOUNT lRet = 0;
    
    if (_pmtxarray)
        _pmtxarray->GetItemCount(&lRet);
        
    return lRet;    
}


//  CDataSrc::_CalcCols
//      Calculate the number of columns in the OSP

DB_LORDINAL CDataSrc::_CalcCols(void)
{
    DB_LORDINAL lRet = 0;
    
    if (_pmtxarray)
        _pmtxarray->GetFieldCount(&lRet);
        
    return lRet;
}


inline BOOL CDataSrc::_IsValidDataRow(DBROWCOUNT iRow)
{
    // Rows are 1-based.  The 0th row refers to label information.
    // -1 means wildcard.

    // The 0th row is NOT a valid data row.
    return (iRow > 0 && iRow <= _cRows);
}


inline BOOL CDataSrc::_IsValidRow(DBROWCOUNT iRow)
{
    // Rows are 1-based.  The 0th row refers to label information.
    // -1 means wildcard.
    return (iRow >= 0 && iRow <= _cRows);
}


inline BOOL CDataSrc::_IsValidCol(DB_LORDINAL iCol)
{
    // Columns are 1-based.  The 0th column refers to header information.
    // -1 means wildcard.
    return (iCol >= 1 && iCol <= _cCols);
}


inline BOOL CDataSrc::_IsValidCell(DBROWCOUNT iRow, DB_LORDINAL iCol)
{
    return _IsValidRow(iRow) && _IsValidCol(iCol);
}


/*-------------------------------------------------------------------------
Purpose: Returns the appdata object of the given row (1-based).  Returns
         NULL if there is none.
*/
IAppData * CDataSrc::_GetAppData(DBROWCOUNT iRow)
{
    IAppData * pappdata = NULL;

    if (_pmtxarray)
    {
        ASSERT(0 < iRow && iRow <= _cRows);
        _pmtxarray->GetAppData(iRow-1, &pappdata);
    }
    
    return pappdata;
}


// Structure used to transfer matrix object thru ISequentialStream()

typedef struct tagARPDSODATA
{
    LOAD_STATE  loadstate;
    DB_LORDINAL cCols;         // count of columns
    DBROWCOUNT  cRows;         // count of rows
    DWORD       dwEnum;        // items to enumerate (ENUM_*)
    IMtxArray * pmtxarray;     // data is stored here
    BSTR        bstrSort;      // sort string
} ARPDSODATA;




/*-------------------------------------------------------------------------
Purpose: ISequentialStream::Read

         Return the matrix object of this datasource object.
         IARPSimpleProvider::TransferData uses this method.
*/
STDMETHODIMP CDataSrc::Read(void * pvData, ULONG cbData, ULONG * pcbRead)
{
    HRESULT hres = E_INVALIDARG;
    
    ASSERT(IS_VALID_WRITE_BUFFER(pvData, BYTE, cbData));
    ASSERT(NULL == pcbRead || IS_VALID_WRITE_PTR(pcbRead, ULONG));
    
    if (pvData)
    {
        ARPDSODATA * pdsodata = (ARPDSODATA *)pvData;

        if (pcbRead)
            *pcbRead = 0;

        if (sizeof(*pdsodata) <= cbData)
        {
            pdsodata->loadstate = _loadstate;
            pdsodata->cCols = _cCols;
            pdsodata->cRows = _cRows;
            pdsodata->dwEnum = _dwEnum;

            pdsodata->pmtxarray = _pmtxarray;
            if (_pmtxarray)
                _pmtxarray->AddRef();
            
            pdsodata->bstrSort = _cbstrSort.Copy();

            if (pcbRead)
                *pcbRead = sizeof(*pdsodata);
        }
        hres = S_OK;
    }
    
    return hres;
}


/*-------------------------------------------------------------------------
Purpose: ISequentialStream::Write

         Set the matrix object of this datasource object.
         IARPSimpleProvider::TransferData uses this method.
*/
STDMETHODIMP CDataSrc::Write(void const * pvData, ULONG cbData, ULONG * pcbWritten)
{
    HRESULT hres = E_INVALIDARG;
    
    ASSERT(IS_VALID_READ_BUFFER(pvData, BYTE, cbData));
    ASSERT(NULL == pcbWritten || IS_VALID_WRITE_PTR(pcbWritten, ULONG));
    
    if (pvData)
    {
        ARPDSODATA * pdsodata = (ARPDSODATA *)pvData;

        if (pcbWritten)
            *pcbWritten = 0;

        if (sizeof(*pdsodata) <= cbData)
        {
            _loadstate = pdsodata->loadstate;
            _cCols = pdsodata->cCols;
            _cRows = pdsodata->cRows;
            _dwEnum = pdsodata->dwEnum;

            // We won't addref this, since the supplier should have done that.
            _pmtxarray = pdsodata->pmtxarray;

            _cbstrSort.Empty();
            _cbstrSort.Attach(pdsodata->bstrSort);

            if (pcbWritten)
                *pcbWritten = sizeof(*pdsodata);
        }
        hres = S_OK;
    }
    
    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::Initialize

         Must be called before enumerating items.
         
*/
STDMETHODIMP CDataSrc::Initialize(IShellAppManager * psam, IARPEvent * parpevt, DWORD dwEnum)
{
    ASSERT(psam);
    ASSERT(IS_VALID_CODE_PTR(parpevt, CEventBroker));

    ATOMICRELEASE(_psam);
    ATOMICRELEASE(_parpevt);

    _psam = psam;
    _psam->AddRef();
    
    _parpevt = parpevt;
    _parpevt->AddRef();

    _dwEnum = dwEnum;
    
    return S_OK;
}


HRESULT CDataSrc::_EnumAppItems(DWORD dwEnum, LPCWSTR pszCategory)
{
    HRESULT hres = E_INVALIDARG;
    IInstalledApp* pAppIns;
    CAppData* pcad;

    ASSERT(NULL == pszCategory || IS_VALID_STRING_PTRW(pszCategory, -1));
    
    switch (dwEnum)
    {
    case ENUM_INSTALLED:
        IEnumInstalledApps* pEnumIns;

        // Now that we have the object, start enumerating the items
        hres = THR(_psam->EnumInstalledApps(&pEnumIns));
        if (SUCCEEDED(hres))
        {
            // Loop through all the apps on the machine, building our table
            while (S_OK == pEnumIns->Next(&pAppIns))
            {
                // If we've been asked to bail, do so
                if (IsKilled())
                {
                    pAppIns->Release();
                    break;
                }
                
                APPINFODATA ai = {0};
                
                // Get the 'fast' app info from the app manager object
                ai.cbSize = sizeof(ai);
                ai.dwMask = AIM_DISPLAYNAME | AIM_VERSION | AIM_PUBLISHER | AIM_PRODUCTID | AIM_REGISTEREDOWNER
                               | AIM_REGISTEREDCOMPANY | AIM_SUPPORTURL | AIM_SUPPORTTELEPHONE | AIM_HELPLINK
                               | AIM_INSTALLLOCATION | AIM_INSTALLDATE | AIM_COMMENTS | AIM_IMAGE
                               | AIM_READMEURL | AIM_CONTACT | AIM_UPDATEINFOURL;
                if (SUCCEEDED(pAppIns->GetAppInfo(&ai)) &&
                    lstrlen(ai.pszDisplayName) > 0)
                {
                    SLOWAPPINFO sai = {0};
                    pAppIns->GetCachedSlowAppInfo(&sai);
                    
                    // Now save all this information away
                    pcad = new CAppData(pAppIns, &ai, &sai);
                    if (pcad)
                    {
                        _pmtxarray->AddItem(pcad, NULL);
                        pcad->Release();
                    }
                    else
                    {
                        // Something failed
                        pAppIns->Release();
                        ClearAppInfoData(&ai);
                    }
                }
                // NOTE: we do NOT release the pointer (pAppIns) here,
                // its lifetime is passed to the CAppData object
            }
            pEnumIns->Release();
            hres = S_OK;
        }
        break;

    case ENUM_PUBLISHED:
        IEnumPublishedApps * pepa;      // Salt 'n...

        // Convert an empty string to a null string if we need to
        if (pszCategory && 0 == *pszCategory)
            pszCategory = NULL;
            
        // Enumerate published apps
        hres = THR(_psam->EnumPublishedApps(pszCategory, &pepa));
        if (SUCCEEDED(hres))
        {
            IPublishedApp * ppa;

            while (S_OK == pepa->Next(&ppa))
            {
                // If we've been asked to bail, do so
                if (IsKilled())
                {
                    ppa->Release();
                    break;
                }
                
                APPINFODATA ai = {0};
                
                // Get the 'fast' app info from the app manager object
                ai.cbSize = sizeof(ai);
                ai.dwMask = AIM_DISPLAYNAME | AIM_VERSION | AIM_PUBLISHER | AIM_PRODUCTID | AIM_REGISTEREDOWNER
                               | AIM_REGISTEREDCOMPANY | AIM_SUPPORTURL | AIM_SUPPORTTELEPHONE | AIM_HELPLINK
                               | AIM_INSTALLLOCATION | AIM_INSTALLDATE | AIM_COMMENTS | AIM_IMAGE;
                if (SUCCEEDED(ppa->GetAppInfo(&ai)) &&
                    lstrlen(ai.pszDisplayName) > 0)
                {
                    PUBAPPINFO pai = {0};
                    pai.cbSize = sizeof(pai);
                    pai.dwMask = PAI_SOURCE | PAI_ASSIGNEDTIME | PAI_PUBLISHEDTIME | PAI_EXPIRETIME | PAI_SCHEDULEDTIME;
                    ppa->GetPublishedAppInfo(&pai);
                    
                    // Now save all this information away
                    pcad = new CAppData(ppa, &ai, &pai);
                    if (pcad)
                    {
                        _pmtxarray->AddItem(pcad, NULL);
                        pcad->Release();
                    }
                    else
                    {
                        // Something failed
                        ppa->Release();
                        ClearAppInfoData(&ai);
                        ClearPubAppInfo(&pai);
                    }
                }
                // NOTE: we do NOT release the pointer (ppa) here,
                // its lifetime is passed to the CAppData object
            }
            pepa->Release();
            hres = S_OK;
        } 
        
        break;

    case ENUM_OCSETUP:
        // Create an object that enums the OCSetup items
        COCSetupEnum * pocse;
        
        pocse = new COCSetupEnum;
        if ( pocse && pocse->EnumOCSetupItems() )
        {
            COCSetupApp * pocsa;

            while ( pocse->Next(&pocsa) )
            {
                // If we've been asked to bail, do so
                if (IsKilled())
                {
                    delete pocsa;
                    break;
                }
            
                // REVIEW: Is it worth it to use an APPINFODATA structure?  COcSetupApp
                // doesn't need this structure but I think it buys us sorting once inside
                // the CAppData array as well as a free implementation of the get_DisplayName
                // property which can be accessed via script.  The data sorting might be
                // important but it might also be worth it to special case that ability.
                APPINFODATA ai = {0};
                ai.cbSize = sizeof(ai);
                ai.dwMask = AIM_DISPLAYNAME;

                if ( pocsa->GetAppInfo(&ai) && (lstrlen(ai.pszDisplayName) > 0) )
                {
                    // Now save all this information away
                    pcad = new CAppData(pocsa, &ai);
                    if (pcad)
                    {
                        _pmtxarray->AddItem(pcad, NULL);
                        pcad->Release();
                    }
                    else
                    {
                        // Something failed
                        delete pocsa;
                        ClearAppInfoData(&ai);
                    }
                }
                // NOTE: we do NOT release the pointer (pocsa) here,
                // its lifetime is passed to the CAppData object
            }
        }
        hres = S_OK;
        break;

    case ENUM_CATEGORIES:
        SHELLAPPCATEGORYLIST sacl = {0};

        // Get the list of categories
        hres = _psam->GetPublishedAppCategories(&sacl);
        if (SUCCEEDED(hres))
        {
            SHELLAPPCATEGORY * psac = sacl.pCategory;

            // If we've been asked to bail, do so
            if (IsKilled())
            {
                ReleaseShellCategory(psac);
                break;
            }
            
            UINT i;

            for (i = 0; i < sacl.cCategories; i++, psac++)
            {
                // Now save all this information away
                pcad = new CAppData(psac);
                if (pcad)
                {
                    _pmtxarray->AddItem(pcad, NULL);
                    pcad->Release();
                }
                else
                {
                    // Something failed
                    ReleaseShellCategory(psac);
                }
            }

            // NOTE: we do NOT release the pointer (sacl) here,
            // its lifetime is passed to the CAppData object
        }
        break;
    }

    return hres;
}
    

/*-------------------------------------------------------------------------
Purpose: CDataSrc::_ThreadStartProc()
         The thread proc for the background thread that enumerates applications
*/
DWORD CDataSrc::_ThreadStartProc()
{
    TraceMsg(TF_TASKS, "[%x] Starting enumerator thread", _dwThreadId);

    // Enumerate the applications, this function does the real work
    _EnumAppItems(_dwEnum, _cbstrCategory);

    // Claim to the world that we are done
    _fAppsEnumed = TRUE;
    _fInEnumOp = FALSE;

    // Tell Trident that dataset has changed
    PostWorkerMessage(WORKERWIN_FIRE_DATASETCHANGED, 0, 0);

    // Call our base class and do clean up. 
    return CWorkerThread::_ThreadStartProc();
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::Recalculate

         Recalculate the number of rows and columns and apply the sorting criteria
         for installed apps, load it's slowappinfo. 
*/
STDMETHODIMP CDataSrc::Recalculate(void)
{
    HRESULT hres = E_PENDING;
    
    if (_fAppsEnumed)
    {
        // Calculate the columns used and cache that away.
        _cCols = _CalcCols();
        _cRows = _CalcRows();
        
        // Presort the items according to the existing sort criteria
        _ApplySortCriteria(FALSE);
        
        if (0 < _cRows)
            _parpevt->RowsAvailable(0, _cRows);
        _parpevt->LoadCompleted();

        // We only get slow info for the installed apps
        if (ENUM_INSTALLED == _dwEnum)
        {
            _loadstate = LS_LOADING_SLOWINFO;

            // Create and kick off the worker thread
            IWorkerEvent * pwrkevt;
            IARPWorker * pmtxworker;

            QueryInterface(IID_IWorkerEvent, (LPVOID *)&pwrkevt);
            ASSERT(pwrkevt);        // this should never fail

            hres = _pmtxarray->QueryInterface(IID_IARPWorker, (LPVOID *)&pmtxworker);
            if (SUCCEEDED(hres))
            {
                // Tell the worker thread to notify us
                pmtxworker->SetListenerWT(pwrkevt);
                hres = pmtxworker->StartWT(THREAD_PRIORITY_BELOW_NORMAL);

                pmtxworker->Release();
            }
            pwrkevt->Release();
        }
        else
            _loadstate = LS_DONE;

        hres = S_OK;
    }
    else
    {
        //
        // ISSUE-2000/09/01-BrianAu  Watch this message.
        // This used to be an assert.  Based on comments from the Trident devs
        // and from what I can glean from this code, the assert is unnecessary.
        // When the enumeration is complete we fire a 'dataMemberChanged' event which
        // results in Recalculate being called again.  Since the _fAppsEnumed
        // flag is set only after enumeration is complete, any prior calls to 
        // this function are harmless.  
        //
        TraceMsg(TF_ALWAYS, "This function should only be called when app enumeration is done");
    }
    
    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::EnumerateItemsAsync

         Enumerate the app items asynchronously.  This  call returns
         when all the items have been enumerated.  The caller should call
         Initialize first.
*/
STDMETHODIMP CDataSrc::EnumerateItemsAsync(void)
{
    HRESULT hres = S_OK;

    ASSERT(_parpevt);      // Caller should have called Initialize() first
    ASSERT(_psam);

    if (!_fInEnumOp)
    {
        _fInEnumOp = TRUE;
        // Make sure the slow info worker thread isn't already running. Stop it if it is.
        _KillMtxWorkerThread();

        // If we already have a list, nuke it
        ATOMICRELEASE(_pmtxarray);

        hres = THR(CMtxArray_CreateInstance(IID_IMtxArray, (LPVOID *)&_pmtxarray));
        if (SUCCEEDED(hres))
        {
            _pmtxarray->Initialize(_dwEnum);

            // Start enumerating items
            SetListenerWT(this);

            // Can't AddRef and worker thread
            hres = THR(StartWT(THREAD_PRIORITY_NORMAL));
        }
        else
            // Let people try again. 
            _fInEnumOp = FALSE;
    }
    else
    {
        // This function should only be called before any enumeration started
        ASSERTMSG(FALSE, "This function should only be called before any enumeration started");
        hres = E_PENDING;
    }
    
    return hres;
}


/*-------------------------------------------------------------------------
Purpose: Sorts the data
*/
HRESULT CDataSrc::_ApplySortCriteria(BOOL bFireDataSetChanged)
{
    HRESULT hres = E_FAIL;

    if (_pmtxarray)
    {
        _pmtxarray->SetSortCriteria(_cbstrSort);
        
        hres = _pmtxarray->SortItems();
        if (SUCCEEDED(hres))
        {
            // Mark the duplicated name entries for published apps
            if ((ENUM_PUBLISHED == _dwEnum) && !StrCmpW(_cbstrSort, L"displayname"))
                _pmtxarray->MarkDupEntries();
        	
            // Tell the databinding agent that our dataset changed
            if (bFireDataSetChanged)
                _parpevt->DataSetChanged();
        }
    }

    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::SetSortCriteria
         Set the sort criterion for the datasource.

         Returns S_OK if the sort criteria is different, S_FALSE if it is
         no different.

         bstrSortExpr       Name of column to sort by ("" = no sorting)
         
*/
STDMETHODIMP CDataSrc::SetSortCriteria(BSTR bstrSortExpr) 
{
    HRESULT hres = S_FALSE;

    // Is this a new sort criteria?
    if (NULL == (LPWSTR)_cbstrSort || 0 != StrCmpIW(bstrSortExpr, _cbstrSort))
    {
        // Yes
        _cbstrSort = bstrSortExpr;
        hres = S_OK;
        _fSortDirty = TRUE;
    }

    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::SetFilter
         Set the filter criterion for the datasource.  Right now this only
         works for published apps, via a category.

         Returns S_OK if the filter criteria is different, S_FALSE if it is
         no different.

         bstrSortExpr       Name of column to sort by ("" = no sorting)
         
*/
STDMETHODIMP CDataSrc::SetFilter(BSTR bstrFilter) 
{
    HRESULT hres = S_FALSE;

    // Is this a new filter criteria?
    if (NULL == (LPWSTR)_cbstrCategory || 0 != StrCmpIW(bstrFilter, _cbstrCategory))
    {
        // Yes
        _cbstrCategory = bstrFilter;
        hres = S_OK;
    }

    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::Sort

         Initiates a sort operation if any of the changes invalidates the 
         existing criteria.
*/
STDMETHODIMP CDataSrc::Sort(void) 
{
    HRESULT hres = S_OK;

    if (_fSortDirty)
    {
        // Is the datasource started?
        if (LS_NOTSTARTED != _loadstate)
        {
            // Yes; we can apply the sort now
            hres = _ApplySortCriteria(TRUE);
            if (SUCCEEDED(hres))
                _fSortDirty = FALSE;
        }
    }

    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::TransferData

         Transfer the contents of given datasource object to this datasource.  
         This is useful for operations that change the dataset in-place, 
         like sorting.
*/
STDMETHODIMP CDataSrc::TransferData(IARPSimpleProvider * parposp)
{
    HRESULT hres;
    ISequentialStream * pstream;
    
    ASSERT(parposp);

    hres = parposp->QueryInterface(IID_ISequentialStream, (LPVOID *)&pstream);
    if (SUCCEEDED(hres))
    {
        IARPWorker * pmtxworker;
        ARPDSODATA dsodata;
        ULONG cb;

        // Transfer the state and data from that datasource to this
        // datasource.
        pstream->Read(&dsodata, sizeof(dsodata), &cb);
        Write(&dsodata, cb, NULL);

        if (_pmtxarray)
        {
            hres = _pmtxarray->QueryInterface(IID_IARPWorker, (LPVOID *)&pmtxworker);
            if (SUCCEEDED(hres))
            {
                // Tell the worker thread that this is the new datasource 
                // object to receive events
                IWorkerEvent * pwrkevt;
                
                QueryInterface(IID_IWorkerEvent, (LPVOID *)&pwrkevt);
                ASSERT(pwrkevt);        // this should never fail
                
                pmtxworker->SetListenerWT(pwrkevt);
                pmtxworker->Release();

                pwrkevt->Release();
            }

            _fAppsEnumed = TRUE;
        }        
        pstream->Release();
    }
    
    return hres;
}


/*-------------------------------------------------------------------------
Purpose: IARPSimpleProvider::DoCommand

         Commit a specific action on the record.  Unlike standard
         ADO commands that affect a recordset, these commands
         are intended to be specific to managing the apps themselves
         (like installing or uninstalling).

         NOTE: this method is called indirectly via script.
*/
STDMETHODIMP CDataSrc::DoCommand(HWND hwndParent, APPCMD appcmd, DBROWCOUNT iRow)
{
    HRESULT hres = S_OK;
    
    IAppData * pappdata = _GetAppData(iRow);
    if (pappdata)
    {
        if (_IsValidDataRow(iRow))
        {
            hres = pappdata->DoCommand(hwndParent, appcmd);

            // Was the app succesfully uninstalled/changed/whatever?
            if (S_OK == hres)
            {
                // Yes
                DBROWCOUNT lDeleted;
                
                switch (appcmd)
                {
                case APPCMD_UNINSTALL:
                    // Fire the event to the databinding agent
                    deleteRows(iRow, 1, &lDeleted); 
                    break;

                case APPCMD_UPGRADE:
                case APPCMD_REPAIR:
                case APPCMD_MODIFY:
                case APPCMD_INSTALL:
                    // Fire the event
                    _parpevt->RowChanged(iRow);
                    break;
                }
            }
        }
        pappdata->Release();
    }
    return hres;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::getRowCount

         Return the number of rows in the table.
*/
STDMETHODIMP CDataSrc::getRowCount(DBROWCOUNT *pcRows)
{
    ASSERT(IS_VALID_WRITE_PTR(pcRows, DBROWCOUNT));

    *pcRows = _cRows;
        
    TraceMsg(TF_DSO, "(Mtx) getRowCount returning %d", _cRows);
    
    return S_OK;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::getColumnCount

         Return the number of columns in the table.
*/
STDMETHODIMP CDataSrc::getColumnCount(DB_LORDINAL *pcCols)
{
    ASSERT(IS_VALID_WRITE_PTR(pcCols, DB_LORDINAL));

    *pcCols = _cCols;

    TraceMsg(TF_DSO, "(Mtx) getColumnCount returning %d", _cCols);

    return S_OK;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::getRWStatus

         Gets the read/write status of a cell, row, column or the 
         entire array.  

         This implementation cannot set the read/write status on any
         cell, so all data cells are presumed to have the default
         access and all column heading cells are presumed to be 
         read-only.  Therefore, we don't keep track of this info
         in the individual cells.

         E_INVALIDARG - returned if indices are out of bounds
*/
STDMETHODIMP CDataSrc::getRWStatus(DBROWCOUNT iRow, DB_LORDINAL iCol, OSPRW *prwStatus)
{
    HRESULT hres = E_INVALIDARG;

    if ((_IsValidRow(iRow) || -1 == iRow) && 
        (_IsValidCol(iCol) || -1 == iCol))
    {
        if (iRow == -1)
        {
            *prwStatus = OSPRW_MIXED;
        }
        else if (iRow == 0)
            *prwStatus = OSPRW_READONLY;
        else
            *prwStatus = OSPRW_DEFAULT;
        hres = S_OK;
    }

    if (FAILED(hres))
        TraceMsg(TF_WARNING, "(Mtx) getRWStatus(%d, %d) failed %s", iRow, iCol, Dbg_GetHRESULT(hres));
    else        
        TraceMsg(TF_DSO, "(Mtx) getRWStatus(%d, %d) returning %s", iRow, iCol, Dbg_GetOSPRW(*prwStatus));
    
    return hres;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::getVariant

         Retrieves a variant value for a cell.
*/
STDMETHODIMP CDataSrc::getVariant(DBROWCOUNT iRow, DB_LORDINAL iCol, OSPFORMAT format, VARIANT * pvar)
{
    HRESULT hres  = E_INVALIDARG;

    TraceMsg(TF_DSO, "(Mtx) getVariant(%d, %d)", iRow, iCol);
    
    ASSERT(IS_VALID_WRITE_PTR(pvar, VARIANT));
    
    if (_IsValidCell(iRow, iCol))
    {
        VARIANT var;

        // Massage col to be 0-based
        iCol--;
        
        // Are they asking for the field name?
        if (0 == iRow)
        {
            // Yes; get the field name
            if (_pmtxarray)
                hres = _pmtxarray->GetFieldName(iCol, &var);
            else
                hres = E_FAIL;
        }
        else
        {
            // No; get the field value
            IAppData * pappdata = _GetAppData(iRow);
            if (pappdata)
            {
                hres = pappdata->GetVariant(iCol, &var);
                pappdata->Release();
            }
            else
                hres = E_FAIL;
        }
            
        if (SUCCEEDED(hres))
        {
            if (OSPFORMAT_RAW == format)
            {
                // Copy the raw variant value
                *pvar = var;
            }
            else if (OSPFORMAT_FORMATTED == format || OSPFORMAT_HTML == format)
            {
                // Consumer wants it in text format
                if (VT_BSTR == var.vt || VT_EMPTY == var.vt)
                {
                    // Already done
                    *pvar = var;
                }
                else if (VT_UI4 == var.vt)
                {
                    // Coerce
                    VarBstrFromUI4( var.lVal, 0, 0, &(pvar->bstrVal));
                    if (pvar->bstrVal != NULL)
                    {
                        pvar->vt = VT_BSTR;
                    }
                    else
                        hres = E_OUTOFMEMORY;
                }
                else
                    hres = E_NOTIMPL;
            }
            else
                hres = E_INVALIDARG;

            if (FAILED(hres))
            {
                VariantClear(&var);
                pvar->vt = VT_BSTR;
                pvar->bstrVal = SysAllocString(L"#Error");
            }
        }
    }

    if (FAILED(hres))
        TraceMsg(TF_WARNING, "(Mtx) getVariant failed %s", Dbg_GetHRESULT(hres));
    
    return hres;
}



/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::setVariant

         Set a cell's variant value from a given variant.  The given variant
         type is coerced into the columns underlying type.
*/
STDMETHODIMP CDataSrc::setVariant(DBROWCOUNT iRow, DB_LORDINAL iCol, OSPFORMAT format, VARIANT var)
{
    HRESULT hres = E_INVALIDARG;

    TraceMsg(TF_DSO, "(Mtx) setVariant(%d, %d)", iRow, iCol);

#ifdef NYI
    if (_IsValidCol(iCol))
    {
        // Massage col to be 0-based
        iCol--;
        
        // Is the data agent trying to change an existing cell?
        if (_IsValidDataRow(iRow))
        {
            // Yes
            IAppData * pappdata = _GetAppData(iRow);
            if (pappdata)
            {
                hres = pappdata->SetVariant(iCol, &var);
                pappdata->Release();
            }
            else
                hres = E_FAIL;
        }
        else
        {
            // No; it wants to add a new row
        }
    }
#else
    hres = E_NOTIMPL;
#endif

    if (FAILED(hres))
        TraceMsg(TF_WARNING, "(Mtx) setVariant failed %s", Dbg_GetHRESULT(hres));
    
    return hres;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::getLocale

         Returns to the consumer the locale of the data we
         are providing.  App management data is in the locale
         of the system, so return an empty bstr.
         
*/
STDMETHODIMP CDataSrc::getLocale(BSTR *pbstrLocale)
{
    TraceMsg(TF_DSO, "(Mtx) getLocale");
    
    *pbstrLocale = SysAllocString(L"");
    return *pbstrLocale ? S_OK : E_OUTOFMEMORY;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::deleteRows

         Used to delete rows from the array.  Bounds are checked
         to make sure that the rows can all be deleted.  Label
         rows cannot be deleted.

         E_INVALIDARG - returned if any rows to be deleted are 
                        out of bounds
*/
STDMETHODIMP CDataSrc::deleteRows(DBROWCOUNT iRow, DBROWCOUNT cRows, DBROWCOUNT *pcRowsDeleted)
{
    HRESULT hres = E_INVALIDARG;

    TraceMsg(TF_DSO, "(Mtx) deleteRows(%d, %d)", iRow, cRows);
    
    *pcRowsDeleted = 0;

    if (_IsValidDataRow(iRow) && cRows >= 0 &&
        _IsValidDataRow(iRow + cRows - 1))
    {
        _parpevt->AboutToDeleteRows(iRow, cRows);

        *pcRowsDeleted = cRows;
        if (cRows > 0)
        {
            //  Delete the rows from the array
            
            _pmtxarray->DeleteItems(iRow - 1, cRows);
            _cRows = _CalcRows();

            //  Notify the event-handler of the deletion
            _parpevt->DeletedRows(iRow, cRows);
        }
        hres = S_OK;
    }

    if (FAILED(hres))
        TraceMsg(TF_WARNING, "(Mtx) deleteRows failed %s", Dbg_GetHRESULT(hres));
    
    return hres;
}


//+-----------------------------------------------------------------------
//
//  Member:    InsertRows()
//
//  Synopsis:  Allows for the insertion of new rows.  This can either be
//             used to insert new rows between existing rows, or to
//             append new rows to the end of the table.  Thus, to
//             insert new rows at the end of the table, a user would
//             specify the initial row as 1 greater than the current
//             row dimension.
//             Note that iRow is checked to ensure that it is within the
//             proper bounds (1..<current # of rows>+1).
//             User cannot delete column heading row.
//
//  Arguments: iRow            rows will be inserted *before* row 'iRow'
//             cRows           how many rows to insert
//             pcRowsInserted  actual number of rows inserted (OUT)
//
//  Returns:   S_OK upon success, i.e. all rows could be inserted.
//             E_INVALIDARG if row is out of allowed bounds.
//             It is possible that fewer than the requested rows were
//             inserted.  In this case, E_OUTOFMEMORY would be returned,
//             and the actual number of rows inserted would be set.
//
//------------------------------------------------------------------------

/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::insertRows

*/
STDMETHODIMP CDataSrc::insertRows(DBROWCOUNT iRow, DBROWCOUNT cRows, DBROWCOUNT *pcRowsInserted)
{
    HRESULT hres  = E_NOTIMPL;

    TraceMsg(TF_DSO, "(Mtx) insertRows(%d, %d)", iRow, cRows);
    
    if (FAILED(hres))
        TraceMsg(TF_WARNING, "(Mtx) insertRows failed %s", Dbg_GetHRESULT(hres));
    
    return hres;
}


//+-----------------------------------------------------------------------
//
//  Member:    Find()
//
//  Synopsis:  Searches for a row matching the specified criteria
//
//  Arguments: iRowStart       The starting row for the search
//             iCol            The column being tested
//             vTest           The value against which cells in the
//                               test column are tested
//             findFlags       Flags indicating whether to search up/down
//                               and whether comparisons are case sensitive.
//             compType        The comparison operator for matching (find a
//                             cell =, >=, <=, >, <, <> the test value)
//             piRowFound      The row with a matching cell [OUT]
//
//  Returns:   S_OK upon success, i.e. a row was found (piRowFound set).
//             E_FAIL upon failure, i.e. a row was not found.
//             E_INVALIDARG if starting row 'iRowStart' or test column 'iCol'
//               are out of bounds.
//             DISP_E_TYPEMISMATCH if the test value's type does not match
//               the test column's type.
//
//------------------------------------------------------------------------

/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::find

*/
STDMETHODIMP CDataSrc::find(DBROWCOUNT iRowStart, DB_LORDINAL iCol, VARIANT vTest,
        OSPFIND findFlags, OSPCOMP compType, DBROWCOUNT *piRowFound)
{
    HRESULT hres  = E_NOTIMPL;

    TraceMsg(TF_DSO, "(Mtx) find(%d, %d)", iRowStart, iCol);
    
    if (FAILED(hres))
        TraceMsg(TF_WARNING, "(Mtx) find failed %s", Dbg_GetHRESULT(hres));
    
    return hres;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::addOLEDBSimpleProviderListener

         Sets or clears a reference to the OSP listener.
*/
STDMETHODIMP CDataSrc::addOLEDBSimpleProviderListener(OLEDBSimpleProviderListener *pospl)
{
    HRESULT hres;

    TraceMsg(TF_DSO, "(Mtx) addOLEDBSimpleProviderListener  <%s>", Dbg_GetLS(_loadstate));
    
    if (_parpevt == NULL)
        hres = E_FAIL;
    else
    {
        _parpevt->SetOSPListener(pospl);
        
        // If the event sink has been added, and we're already loaded,
        // then fire transferComplete, because we probably couldn't before.
        if (LS_NOTSTARTED < _loadstate)
            _parpevt->LoadCompleted();

        hres = S_OK;
    }

    return hres;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::removeOLEDBSimpleProviderListener

*/
STDMETHODIMP CDataSrc::removeOLEDBSimpleProviderListener(OLEDBSimpleProviderListener * pospl)
{
    if (_parpevt && S_OK == _parpevt->IsOSPListener(pospl))
    {
        TraceMsg(TF_DSO, "(Mtx) removeOLEDBSimpleProviderListener");
        
        _parpevt->SetOSPListener(NULL);
    }
    return S_OK;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::getEstimatedRows

         Returns an estimated number of rows in the matrix.
         Return -1 if unknown.
*/
STDMETHODIMP CDataSrc::getEstimatedRows(DBROWCOUNT *pcRows)
{
    if (LS_NOTSTARTED == _loadstate)
        *pcRows = -1;
    else
        *pcRows = _cRows;

    TraceMsg(TF_DSO, "(Mtx) getEstimatedRows returning %d  <%s>", *pcRows, Dbg_GetLS(_loadstate));
    
    return S_OK;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::isAsync

*/
STDMETHODIMP CDataSrc::isAsync(BOOL *pbAsync)
{
    // This OSP always behaves as if it is async.  Specifically, we always fire
    // TransferComplete, even if we have to buffer the notification until our
    // addOLEDBSimplerProviderListener is actually called.
    *pbAsync = TRUE;
    return S_OK;
}


/*----------------------------------------------------------
Purpose: OLEDBSimpleProvider::stopTransfer

         The data download has been cancelled.
*/
STDMETHODIMP CDataSrc::stopTransfer()
{
    TraceMsg(TF_DSO, "(Mtx) stopTransfer  <%s>", Dbg_GetLS(_loadstate));
    
    //  Force the load state into UNINITIALISED or LOADED ...
    //
    switch (_loadstate)
    {
    case LS_NOTSTARTED:
    case LS_DONE:
        // No need to do anything, because we either haven't started
        // or are already finished.
        break;

    case LS_LOADING_SLOWINFO:
        // Stop the worker thread.
        _KillMtxWorkerThread();

        // Say we're done
        _loadstate = LS_DONE;
        
        TraceMsg(TF_DSO, "(Mtx) Setting state to <%s>", Dbg_GetLS(_loadstate));
        
        // Fire an abort event
        _parpevt->LoadAborted();
        break;
    }

    return S_OK;
}


/*-------------------------------------------------------------------------
Purpose: Helper method to kill the worker thread
*/
HRESULT CDataSrc::_KillMtxWorkerThread(void)
{
    HRESULT hres = S_OK;

    if (_pmtxarray)
    {
        IARPWorker * pmtxworker;
        
        hres = _pmtxarray->QueryInterface(IID_IARPWorker, (LPVOID *)&pmtxworker);
        if (SUCCEEDED(hres))
        {
            hres = pmtxworker->KillWT();
            pmtxworker->Release();
        }
    }
    return hres;
}


/*----------------------------------------------------------
Purpose: Create-instance function for CDataSrc

*/
HRESULT CDataSrc_CreateInstance(REFIID riid, LPVOID * ppvObj)
{
    HRESULT hres = E_OUTOFMEMORY;

    *ppvObj = NULL;
    
    CDataSrc * pObj = new CDataSrc();
    if (pObj)
    {
        hres = pObj->QueryInterface(riid, ppvObj);
        pObj->Release();
    }

    return hres;
}

#endif //DOWNLEVEL_PLATFORM
