|
|
//=============================================================================
// File: gather.cpp
// Author: a-jammar
// Covers: CDataGatherer
//
// Copyright (c) 1998-1999 Microsoft Corporation
//
// For usage, see the header file.
//
// The data gathering object maintains a tree of categories. Category
// information is maintained internally, referenced by a ID. These IDs are
// used so that category objects, which will be created and passed outside
// of this object, can refer to the category information stored internally.
// If a category object held externally is no longer valid (possibly from
// a refresh operation), the ID will no longer be used within this object.
// IDs are DWORDS, will start at 1, and increase sequentially. Running out
// of IDs will not be a problem.
//=============================================================================
#include "stdafx.h"
#include "gather.h"
#include "gathint.h"
#pragma warning(disable : 4099)
#include "wbemcli.h"
#pragma warning(default : 4099)
#include "resource.h"
#include "resrc1.h"
//-----------------------------------------------------------------------------
// The constructor needs to note that the object isn't initialized.
// The destructor removes all the categories.
//-----------------------------------------------------------------------------
CDataGatherer::CDataGatherer() { m_fInitialized = FALSE; m_pProvider = NULL; m_dwRootID = 0; // zero is used as a null category ID
m_dwNextFreeID = 1; // so the first real ID should be one
m_complexity = BASIC; m_fDeferredPending = FALSE; m_dwDeferredError = GATH_ERR_NOERROR; m_fTemplatesLoaded = FALSE; m_cInRefresh = 0;
ResetLastError(); }
CDataGatherer::~CDataGatherer() { if (m_pProvider) delete m_pProvider;
if (m_fInitialized) RemoveAllCategories(); }
//=============================================================================
// Functions called directly on CDataGatherer objects.
//=============================================================================
//-----------------------------------------------------------------------------
// This method is used to get more information about the last error in a
// gatherer or category member function call. This will return an error code,
// or zero for OK. Note that a successful method call will reset the value
// returned by this method.
//-----------------------------------------------------------------------------
DWORD CDataGatherer::GetLastError() { return m_dwLastError; }
DWORD CDataGatherer::GetLastError(DWORD dwID) { INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal); if (pInternal) return pInternal->m_dwLastError; return GATH_ERR_NOERROR; }
//-----------------------------------------------------------------------------
// This Create method delegates the work to the methods for setting a
// connection to another machine.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::Create(LPCTSTR szMachine) { ResetLastError();
// Create can only be called once in the lifetime of the object.
ASSERT(!m_fInitialized); if (m_fInitialized) return FALSE;
m_fInitialized = TRUE; // set initialize flag so SetConnect will run
m_fInitialized = SetConnect(szMachine); return m_fInitialized; }
//-----------------------------------------------------------------------------
// LoadTemplates is used to load the template information from this DLL (the
// default info) and any other DLLs with registry entries.
//-----------------------------------------------------------------------------
void CDataGatherer::LoadTemplates() { TCHAR szBaseKey[] = _T("SOFTWARE\\Microsoft\\Shared Tools\\MSInfo\\templates"); HKEY hkeyBase;
if (!m_fTemplatesLoaded) { m_fTemplatesLoaded = TRUE;
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBaseKey, 0, KEY_READ, &hkeyBase)) { CTemplateFileFunctions::LoadTemplateDLLs(hkeyBase, this); RegCloseKey(hkeyBase); } else { CTemplateFileFunctions::LoadTemplateDLLs(NULL, this); }
if (!m_strCategory.IsEmpty()) CTemplateFileFunctions::ApplyCategories(m_strCategory, this); } }
//-----------------------------------------------------------------------------
// This method is used to create a WBEM connection to the specified machine.
// If the string parameter is null or empty, then we connect to this machine.
// Create the connection by creating a CDataProvider object.
//
// UPDATE: we're going to delay creating the provider until it is actually
// needed. This should speed up our creation process, and keep us being
// good citizens in computer management.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::SetConnect(LPCTSTR szMachine) { ASSERT(m_fInitialized);
ResetLastError(); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
// Delete an existing provider object (it isn't possible to redirect
// an existing provider - we need to create a new one).
if (m_pProvider) { delete m_pProvider; m_pProvider = NULL; }
// Save the name of the machine for the deferred creation, and note that
// there is deferred work to do.
m_strDeferredProvider = szMachine; m_fDeferredPending = TRUE; return TRUE; }
//-----------------------------------------------------------------------------
// Refresh all the information by refreshing the root category with a
// recursive flag.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::Refresh(volatile BOOL *pfCancel) { ASSERT(m_fInitialized);
ResetLastError(); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
// Multiple calls to LoadTemplates don't matter - it will only load
// the DLL template information once.
LoadTemplates();
if (m_dwRootID != 0) return RefreshCategory(m_dwRootID, TRUE, pfCancel);
return TRUE; }
//-----------------------------------------------------------------------------
// Set the complexity of the data shown to the user.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::SetDataComplexity(DataComplexity complexity) { ASSERT(m_fInitialized);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
ASSERT(complexity == BASIC || complexity == ADVANCED); m_complexity = complexity; return TRUE; }
//-----------------------------------------------------------------------------
// This method is used to allocate a new CDataCategory object to represent
// the root category, and return a pointer to the new object. The caller is
// responsible for eventually deallocating the object. We use a helper function
// to construct an object for a given ID number.
//-----------------------------------------------------------------------------
CDataCategory * CDataGatherer::GetRootDataCategory() { ASSERT(m_fInitialized);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return NULL; }
LoadTemplates();
if (m_dwRootID != 0) return BuildDataCategory(m_dwRootID);
return NULL; }
//-----------------------------------------------------------------------------
// This method is used to convert a category identifier (static, non-localized)
// to a path to a category (all the category names to the identified category,
// not including the root, delimited by backslashes).
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetCategoryPath(const CString & strIdentifier, CString & strPath) { CString strTempPath; DWORD dwID = m_dwRootID;
ASSERT(m_fInitialized);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
if (dwID && FindCategoryByIdentifer(strIdentifier, strTempPath, dwID)) { // The path we have right now includes the root category name. This is
// unnecessary, so we remove it.
int iFirstDelimiter = strTempPath.Find(_T('\\')); if (iFirstDelimiter > 0) strPath = strTempPath.Right(strTempPath.GetLength() - iFirstDelimiter); else strPath = strTempPath;
return TRUE; }
SetLastError(GATH_ERR_BADCATIDENTIFIER); return FALSE; }
//-----------------------------------------------------------------------------
// Find the strSearch string in the gathered data. Start at the strPath
// category on line iLine (if strPath is empty, start at the root). Return
// the first match in strPath & iLine. If iLine is -1, then search the
// category name as well.
//
// We want to search from top to bottom on a fully expanded tree view of the
// data categories - meaning we should use a depth first search.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::Find(MSI_FIND_STRUCT *pFind) { // Parameter checking.
ASSERT(m_fInitialized);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
ASSERT(pFind); if (!pFind) return FALSE;
if (m_dwRootID == 0) return FALSE;
ASSERT(pFind->m_fSearchCategories || pFind->m_fSearchData); // should search something
ASSERT(!pFind->m_strSearch.IsEmpty()); // should search for something
ASSERT(pFind->m_fSearchCategories || pFind->m_iLine != -1); // contradiction
ASSERT(!pFind->m_strPath.IsEmpty() || pFind->m_iLine <= 0); // can't start in middle of non-category
// The path passed in doesn't start with the root category, but internally
// we want to deal with paths that include the root.
CString strRootName; if (GetName(m_dwRootID, strRootName)) { if (!pFind->m_strPath.IsEmpty() && pFind->m_strPath[0] == _T('\\')) pFind->m_strPath = strRootName + pFind->m_strPath; else pFind->m_strPath = strRootName + CString("\\") + pFind->m_strPath;
if (pFind->m_strParentPath.Find(strRootName) != 0) { if (!pFind->m_strParentPath.IsEmpty() && pFind->m_strParentPath[0] == _T('\\')) pFind->m_strParentPath = strRootName + pFind->m_strParentPath; else pFind->m_strParentPath = strRootName + CString("\\") + pFind->m_strParentPath; } }
// Find the ID of the category to start searching from.
CString strPath = pFind->m_strPath; int iLine; DWORD dwID = FindCategoryByPath(strPath); INTERNAL_CATEGORY * pInternalCat;
// A line number of -1 is used to identify a category name. If there
// is a category path, use whatever line was passed in. Otherwise,
// use 0 or -1 depending on whether we are supposed to search cat names.
if (pFind->m_strPath.IsEmpty()) iLine = (pFind->m_fSearchCategories) ? -1 : 0; else iLine = pFind->m_iLine;
// If we couldn't find an ID (empty path or bad path), start from the root.
if (dwID == 0) dwID = m_dwRootID;
// If this search is case insensitive, convert the string we're looking
// for to all uppercase (also done to strings we're scanning).
if (!pFind->m_fCaseSensitive) pFind->m_strSearch.MakeUpper();
// This loop is used to perform a recursive search of the current category,
// and then continue with first the next sibling of the category, then the
// next sibling of the parent category. We traverse up the tree until
// we reach a category which is not a child of m_strParentPath. In this way,
// we can search a the scope pane's tree control from top to bottom starting
// at any arbitrary category.
pFind->m_fNotFound = FALSE; pFind->m_fFound = FALSE; pFind->m_fCancelled = FALSE;
while (dwID) { // Get the internal category pointer for the ID we're searching.
pInternalCat = GetInternalRep(dwID);
// This should never happen, but it would be better to fail the find than crash.
if (pInternalCat == NULL) return FALSE;
// Search this ID and all of it's children using a recursive function.
if (RecursiveFind(pInternalCat, pFind, iLine, strPath)) { // Strip off the root category from the front of the path before we return it.
int iFirstDelimiter = strPath.Find(_T('\\')); if (iFirstDelimiter > 0) strPath = strPath.Right(strPath.GetLength() - iFirstDelimiter); else strPath.Empty(); // There is no delimiter, the path is just the root.
pFind->m_strPath = strPath; pFind->m_iLine = iLine; pFind->m_fFound = TRUE;
return TRUE; }
// If the caller has cancelled the find, return.
if (pFind->m_pfCancel && *pFind->m_pfCancel) { pFind->m_fCancelled = TRUE; return TRUE; }
// If we didn't find it in the first category we were looking in, reset
// the line variable so we search all of each of the next categories.
iLine = ((pFind->m_fSearchCategories) ? -1 : 0); // If we didn't find a match, keep looking - first in the next siblings
// of the current ID, then in the parent's next sibling, and so on.
while (pInternalCat && pInternalCat->m_dwNextID == 0) pInternalCat = (pInternalCat->m_dwParentID) ? GetInternalRep(pInternalCat->m_dwParentID) : NULL; if (pInternalCat) { dwID = pInternalCat->m_dwNextID;
if (!pFind->m_strParentPath.IsEmpty() && !IsChildPath(pInternalCat, pFind->m_strParentPath)) break; } else break; }
// If we reach here, the data wasn't found (and the find wasn't cancelled),
// so set the struct members accordingly and return TRUE.
pFind->m_fNotFound = TRUE; return TRUE; }
//=============================================================================
// Functions implementing CDataCategory (and derived) object behavior.
//=============================================================================
//-----------------------------------------------------------------------------
// This method (usually called by a category object) returns a BOOL indicating
// is the supplied ID refers to a valid category. Note: this method should have
// no side effects, as it is used extensively within ASSERTs.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::IsValidDataCategory(DWORD dwID) { INTERNAL_CATEGORY * pCheck;
if (dwID != 0 && m_mapCategories.Lookup((WORD) dwID, (void * &) pCheck)) { ASSERT(pCheck && pCheck->m_dwID == dwID); return TRUE; } return FALSE; }
//-----------------------------------------------------------------------------
// This method (called by a category object) is used to get the name
// of the category.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetName(DWORD dwID, CString & strName) { INTERNAL_CATEGORY * pInternalCat;
ASSERT(m_fInitialized);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
// Look up the internal representation for the specified category.
pInternalCat = GetInternalRep(dwID);
if (pInternalCat) { strName = pInternalCat->m_categoryName.m_strText; return TRUE; }
return FALSE; }
//-----------------------------------------------------------------------------
// GetRelative is used by the CDataCategory (or derived) object to navigate
// through the category tree. The relative enumeration tells which direction
// in the tree to navigate.
//-----------------------------------------------------------------------------
CDataCategory * CDataGatherer::GetRelative(DWORD dwID, Relative relative) { INTERNAL_CATEGORY * pInternalCat; DWORD dwRelativeID = 0;
ASSERT(m_fInitialized);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return NULL; }
// The first call to GetRelative will be using the root node, looking
// for it's children. This is when we want to do all of the work we've
// deferred, like creating the provider and loading the template files.
if (m_fDeferredPending) { m_dwDeferredError = GATH_ERR_NOERROR;
// Call GetProvider to connect to WBEM.
// if (GetProvider() == NULL)
// m_dwDeferredError = m_dwLastError;
m_fDeferredPending = FALSE; }
if (m_dwDeferredError != GATH_ERR_NOERROR) return NULL;
// Look up the internal representation for the specified category.
pInternalCat = GetInternalRep(dwID); if (pInternalCat == NULL) { SetLastError(GATH_ERR_BADCATEGORYID); return NULL; }
// Then try to return the relative category, if there is one.
switch (relative) { case PARENT: dwRelativeID = pInternalCat->m_dwParentID; break; case CHILD: dwRelativeID = pInternalCat->m_dwChildID; break; case NEXT_SIBLING: dwRelativeID = pInternalCat->m_dwNextID; break; case PREV_SIBLING: dwRelativeID = pInternalCat->m_dwPrevID; break; }
if (pInternalCat && dwRelativeID) return BuildDataCategory(dwRelativeID); else return NULL; }
//-----------------------------------------------------------------------------
// This method returns whether or not a specified category is dynamic. This
// can be determined by checking a flag.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::IsCategoryDynamic(DWORD dwID) { ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); if (pInternal) return pInternal->m_fDynamic; return FALSE; }
//-----------------------------------------------------------------------------
// This method returns whether or not a specified category has dynamic
// children. We need to use recursion to look at all the children, making
// this an expensive operation.
//
// TBD: compute this all in one pass and save it
// NOTE: currently we don't have dynamic categories
//-----------------------------------------------------------------------------
BOOL CDataGatherer::HasDynamicChildren(DWORD dwID, BOOL /* fRecursive */) { if (dwID == 0) return FALSE;
return FALSE; }
//-----------------------------------------------------------------------------
// Get the count of columns from the internal representation - only count
// columns with the appropriate data complexity (BASIC or ADVANCED).
//-----------------------------------------------------------------------------
DWORD CDataGatherer::GetColumnCount(DWORD dwID) { DWORD dwCount = 0;
ASSERT(m_fInitialized); if (!m_fInitialized) return 0;
if (GetLastError() || GetLastError(dwID)) return 1;
INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal && pInternal->m_fListView); if (pInternal && pInternal->m_fListView) { GATH_FIELD * pCol = pInternal->m_pColSpec; while (pCol) { if (m_complexity == ADVANCED || pCol->m_datacomplexity == BASIC) dwCount++; pCol = pCol->m_pNext; } }
return dwCount; }
//-----------------------------------------------------------------------------
// Get the count of rows from the internal representation - taking into
// account the data complexity.
//-----------------------------------------------------------------------------
DWORD CDataGatherer::GetRowCount(DWORD dwID) { DWORD dwCount = 0;
ASSERT(m_fInitialized); if (!m_fInitialized) return 0;
if (GetLastError() || GetLastError(dwID)) return 1;
INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal && pInternal->m_fListView);
if(NULL == pInternal) return 0;
if (pInternal && pInternal->m_fListView) { for (int i = 0; i < (int)pInternal->m_dwLineCount; i++) if (m_complexity == ADVANCED || pInternal->m_apLines[i]->m_datacomplexity == BASIC) dwCount++; }
// If the row count is zero, return a count of one instead. This allows us
// to display a message for no data. This is not true if there are
// sub-categories for this category.
if (dwCount == 0 && pInternal->m_dwChildID == 0) dwCount = 1;
return dwCount; }
//-----------------------------------------------------------------------------
// This method returns the data complexity (BASIC or ADVANCED) for a row.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetRowDataComplexity(DWORD dwID, DWORD nRow, DataComplexity & complexity) { DWORD dwCount = nRow;
ASSERT(m_fInitialized); if (!m_fInitialized) return 0;
INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal && pInternal->m_fListView); if (pInternal && pInternal->m_fListView) { // If there are no lines and we're be queried about the first line,
// it means that information is being requested for the "no instances..."
// message. Return as if it were BASIC information.
if (nRow == 0 && pInternal->m_dwLineCount == 0) { complexity = BASIC; return TRUE; }
for (int i = 0; i < (int)pInternal->m_dwLineCount; i++) if (m_complexity == ADVANCED || pInternal->m_apLines[i]->m_datacomplexity == BASIC) { if (dwCount == 0) { complexity = pInternal->m_apLines[i]->m_datacomplexity; return TRUE; } dwCount -= 1; } }
return FALSE; }
//-----------------------------------------------------------------------------
// Get the caption for the specified column. Stored in internal representation.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetColumnCaption(DWORD dwID, DWORD nColumn, CString & strCaption) { INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal && pInternal->m_fListView); ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
if (GetLastError() || GetLastError(dwID)) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); strCaption.LoadString(IDS_DESCRIPTION); return TRUE; }
// The column caption is a refreshed value (not something read from the
// template file). We need to look it up in the array of refreshed
// column headers.
if (pInternal && pInternal->m_fListView) if (nColumn < pInternal->m_dwColCount) { // Get the actual index for the column information. This may be
// higher than the index parameter, if we are currently showing
// BASIC information (we need to skip over advanced columns).
DWORD nActualColumn = 0;
if (m_complexity == BASIC) { GATH_FIELD * pCol = pInternal->m_pColSpec; int iCount = (int) nColumn;
do { // Skip over any advanced categories.
while (pCol && pCol->m_datacomplexity == ADVANCED) { pCol = pCol->m_pNext; nActualColumn++; }
iCount--; nActualColumn++; pCol = pCol->m_pNext; } while (pCol && (iCount >= 0));
if (iCount >= 0) return FALSE; else nActualColumn--; // we went one too far
} else nActualColumn = nColumn;
strCaption = pInternal->m_aCols[nActualColumn].m_strText; return TRUE; }
return FALSE; }
//-----------------------------------------------------------------------------
// Return the width of the specified column. Stored in internal representation.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetColumnWidth(DWORD dwID, DWORD nColumn, DWORD &cxWidth) { ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
if (GetLastError() || GetLastError(dwID)) { cxWidth = 600; return TRUE; }
GATH_FIELD * pField = GetColumnField(dwID, nColumn); if (pField) { cxWidth = pField->m_usWidth; return TRUE; } return FALSE; }
//-----------------------------------------------------------------------------
// This method returns how a specified column should be sorted.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetColumnSort(DWORD dwID, DWORD nColumn, MSIColumnSortType & sorttype) { ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
if (GetLastError() || GetLastError(dwID)) { sorttype = NOSORT; return TRUE; }
GATH_FIELD * pField = GetColumnField(dwID, nColumn); if (pField) { sorttype = pField->m_sort; return TRUE; } return FALSE; }
//-----------------------------------------------------------------------------
// This method returns the data complexity (BASIC or ADVANCED) for a column.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetColumnDataComplexity(DWORD dwID, DWORD nColumn, DataComplexity & complexity) { ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
if (GetLastError() || GetLastError(dwID)) { complexity = BASIC; return TRUE; }
GATH_FIELD * pField = GetColumnField(dwID, nColumn); if (pField) { complexity = pField->m_datacomplexity; return TRUE; } return FALSE; }
//-----------------------------------------------------------------------------
// This (private) method is used to get the internal field representation
// of the specified column.
//-----------------------------------------------------------------------------
GATH_FIELD * CDataGatherer::GetColumnField(DWORD dwID, DWORD nColumn) { INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal && pInternal->m_fListView); if (pInternal && pInternal->m_fListView) { // We need to scan the collection of column fields to find the requested one.
GATH_FIELD * pField = pInternal->m_pColSpec; DWORD dwIndex = nColumn;
while (pField) { if (m_complexity == ADVANCED || pField->m_datacomplexity == BASIC) { if (dwIndex <= 0) return pField; dwIndex--; } pField = pField->m_pNext; } }
return NULL; }
//-----------------------------------------------------------------------------
// This method returns the value for a specified row and column number,
// in both string and DWORD format.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::GetValue(DWORD dwID, DWORD nRow, DWORD nColumn, CString &strValue, DWORD &dwValue) { ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
if (GetLastError() || GetLastError(dwID)) { if (nRow == 0 && nColumn == 0) { if (GetLastError()) strValue = GetErrorText(); else strValue = GetErrorText(dwID); } else strValue.Empty(); return TRUE; }
INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal && pInternal->m_fListView); if (pInternal && pInternal->m_fListView) { if (nRow == 0 && pInternal->m_dwLineCount == 0) { // Return the string from the template file for "no instances",
// for the first column.
if (nColumn == 0) strValue = pInternal->m_strNoInstances; else strValue.Empty(); return TRUE; }
// Otherwise, look up the cached value. First get the real row and
// column indices, which might be different from the specified
// indices (because of the BASIC/ADVANCED issue).
int iActualRow = 0; int iActualCol = 0;
// Get the actual row index.
if (m_complexity == BASIC) { int iCount = (int) nRow;
do { // Skip over any advanced rows.
while (iActualRow < (int)pInternal->m_dwLineCount && pInternal->m_apLines[iActualRow]->m_datacomplexity == ADVANCED) iActualRow++;
iCount -= 1; iActualRow += 1; } while ((iActualRow < (int)pInternal->m_dwLineCount) && (iCount >= 0));
if (iCount >= 0) return FALSE; else iActualRow -= 1; // we went one too far
} else iActualRow = nRow;
// Get the actual column index.
if (m_complexity == BASIC) { GATH_FIELD * pCol = pInternal->m_pColSpec; int iCount = (int) nColumn;
do { // Skip over any advanced columns.
while (pCol && pCol->m_datacomplexity == ADVANCED) { pCol = pCol->m_pNext; iActualCol++; }
iCount--; iActualCol++; pCol = pCol->m_pNext; } while (pCol && (iCount >= 0));
if (iCount >= 0) return FALSE; else iActualCol--; // we went one too far
} else iActualCol = nColumn;
// Retrieve the data using the actual indices.
if (iActualRow >= 0 && iActualRow < (int)pInternal->m_dwLineCount) if (iActualCol >= 0 && iActualCol < (int)pInternal->m_dwColCount) { strValue = pInternal->m_apLines[iActualRow]->m_aValue[iActualCol].m_strText; dwValue = pInternal->m_apLines[iActualRow]->m_aValue[iActualCol].m_dwValue; return TRUE; } }
return FALSE; }
//=============================================================================
// Functions used internally to CDataGatherer, or by other friend classes.
//=============================================================================
//-----------------------------------------------------------------------------
// This methods deletes all of the internal category representations
// and empties the map.
//-----------------------------------------------------------------------------
void CDataGatherer::RemoveAllCategories() { INTERNAL_CATEGORY * pInternalCat; WORD key;
ASSERT(m_fInitialized); if (!m_fInitialized) return;
for (POSITION pos = m_mapCategories.GetStartPosition(); pos != NULL;) { m_mapCategories.GetNextAssoc(pos, key, (void * &) pInternalCat); ASSERT(pInternalCat); if (pInternalCat) delete pInternalCat; } m_mapCategories.RemoveAll();
// Reset the root ID. Don't reset the next free ID, because we don't want
// the IDs to overlap during the lifetime of the object (otherwise some
// categories passed out might refer to the wrong internal category).
m_dwRootID = 0; }
//-----------------------------------------------------------------------------
// This method is used to return a pointer to the internal representation of
// the category.
//-----------------------------------------------------------------------------
INTERNAL_CATEGORY * CDataGatherer::GetInternalRep(DWORD dwID) { INTERNAL_CATEGORY * pInternalCat;
ASSERT(m_fInitialized); if (!m_fInitialized) return NULL;
if (m_mapCategories.Lookup((WORD) dwID, (void * &) pInternalCat)) { ASSERT(pInternalCat && pInternalCat->m_dwID == dwID); return pInternalCat; } else { ASSERT(FALSE); return NULL; } }
//-----------------------------------------------------------------------------
// Return the CDataProvider object (well, a pointer to the object). If there
// isn't one yet, then create one.
//-----------------------------------------------------------------------------
CDataProvider * CDataGatherer::GetProvider() { ASSERT(m_fInitialized); if (!m_fInitialized) return NULL;
if (m_pProvider == NULL) { // If the m_pProvider member is NULL, then we need to create a new one.
// Create the provider for the value stored in the deferred provider. If
// it is set, then we are doing the deferred creation of a provider for
// another machine. If it's empty, we're looking at the local machine.
// If the Create fails, it will call SetLastError saying why.
m_pProvider = new CDataProvider; if (m_pProvider == NULL) SetLastError(GATH_ERR_ALLOCATIONFAILED);
if (!m_pProvider->Create(m_strDeferredProvider, this)) { delete m_pProvider; m_pProvider = NULL; } }
return m_pProvider; }
//-----------------------------------------------------------------------------
// This method resets the internal refreshed flag. It's called if the provider
// is pointed to a different computer, for example, to make each category get
// fresh data to display.
//-----------------------------------------------------------------------------
void CDataGatherer::ResetCategoryRefresh() { INTERNAL_CATEGORY * pInternalCat; WORD key;
for (POSITION pos = m_mapCategories.GetStartPosition(); pos != NULL;) { m_mapCategories.GetNextAssoc(pos, key, (void * &) pInternalCat); ASSERT(pInternalCat); if (pInternalCat) pInternalCat->m_fRefreshed = FALSE; } }
//-----------------------------------------------------------------------------
// This method is used to refresh the contents of the internal category struct.
// Doing this means refreshing the category name, its columns, and building
// a list of lines based on the list of line specifiers in the category.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::RefreshCategory(DWORD dwID, BOOL fRecursive, volatile BOOL *pfCancel, BOOL fSoftRefresh) { INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID);
ASSERT(m_fInitialized); if (!m_fInitialized) return FALSE;
if (pInternal == NULL) return FALSE;
// Check to see if the refresh operation has been cancelled.
if (pfCancel && *pfCancel == TRUE) { TRACE0("-- CDataGatherer::RefreshCategory() refresh cancelled by caller\n"); return FALSE; }
// If this is the first call to this recursive function, reset the
// cache of WBEM enumerator pointers.
if (m_cInRefresh == 0 && m_pProvider) m_pProvider->m_enumMap.Reset(); m_cInRefresh++;
if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = dwID;
// Remove the cached items in the CDataProvider object.
if (m_pProvider) m_pProvider->ClearCache();
// If this is a soft refresh, and this category has been refreshed at least once,
// skip the refresh operation. An example of a soft refresh would be the user
// clicking on the category for the first time, where we would want to skip the
// refresh if a global refresh had been done previously.
if (!pInternal->m_fRefreshed || !fSoftRefresh) { if (!CRefreshFunctions::RefreshColumns(this, pInternal)) { TRACE0("-- CDataGatherer::RefreshCategory() failed at RefreshColumns\n"); m_cInRefresh--; if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = 0; return FALSE; // RefreshValue will set last error
}
// The RefreshLines function returns a CPtrList of pointers to line structures. These
// pointers need to be copied to the pInternal->m_apLines array.
CPtrList listLinePtrs; if (CRefreshFunctions::RefreshLines(this, pInternal->m_pLineSpec, pInternal->m_dwColCount, listLinePtrs, pfCancel)) { if (pInternal->m_apLines && pInternal->m_dwLineCount) { for (DWORD dwIndex = 0; dwIndex < pInternal->m_dwLineCount; dwIndex++) delete pInternal->m_apLines[dwIndex]; delete [] pInternal->m_apLines; }
// Move the contents of listLinePtrs to the array of line pointers in the internal struct.
pInternal->m_dwLineCount = (DWORD) listLinePtrs.GetCount(); if (pInternal->m_dwLineCount) { pInternal->m_apLines = new GATH_LINE *[pInternal->m_dwLineCount]; if (pInternal->m_apLines) { DWORD dwIndex = 0; for (POSITION pos = listLinePtrs.GetHeadPosition(); pos != NULL;) { ASSERT(dwIndex < (DWORD) listLinePtrs.GetCount()); pInternal->m_apLines[dwIndex] = (GATH_LINE *) listLinePtrs.GetNext(pos); dwIndex++; } } else { // If there was an error, we need to deallocate the lines.
GATH_LINE * pLine; for (POSITION pos = listLinePtrs.GetHeadPosition(); pos != NULL;) { pLine = (GATH_LINE *) listLinePtrs.GetNext(pos) ; if (pLine) delete pLine; }
TRACE0("-- CDataGatherer::RefreshCategory() failed allocating m_apLines\n"); SetLastError(GATH_ERR_ALLOCATIONFAILED); m_cInRefresh--; if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = 0; return FALSE; } }
pInternal->m_fRefreshed = TRUE; } else { TRACE0("-- CDataGatherer::RefreshCategory() failed at RefreshLines\n"); m_cInRefresh--; if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = 0; return FALSE; // RefreshLines will set last error
} }
if (fRecursive) { INTERNAL_CATEGORY * pChild; DWORD dwChildID = pInternal->m_dwChildID;
while (dwChildID) { if (!RefreshCategory(dwChildID, TRUE, pfCancel)) { m_cInRefresh--; if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = 0; return FALSE; } pChild = GetInternalRep(dwChildID); if (pChild) dwChildID = pChild->m_dwNextID; else break; } } else { // Even if we aren't recursive, we should refresh the names of the sub
// categories, since they might be enumerated before they are refreshed.
INTERNAL_CATEGORY * pChild; DWORD dwChildID = pInternal->m_dwChildID;
while (dwChildID) { pChild = GetInternalRep(dwChildID); if (pChild) { if (!CRefreshFunctions::RefreshValue(this, &pChild->m_categoryName, &pChild->m_fieldName)) { m_cInRefresh--; if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = 0; return FALSE; } dwChildID = pChild->m_dwNextID; } else break; } }
m_cInRefresh--; if (m_pProvider) m_pProvider->m_dwRefreshingCategoryID = 0; return TRUE; }
//-----------------------------------------------------------------------------
// Sets the last error (the value returned by GetLastError) to the specified
// DWORD value.
//-----------------------------------------------------------------------------
void CDataGatherer::SetLastError(DWORD dwError) { // Making a change - disable all of the error reset calls (a few will
// be done explicitly.
if (dwError != GATH_ERR_NOERROR) m_dwLastError = dwError;
#ifdef _DEBUG
if (dwError) TRACE1("-- SetLastError(0x%08x)\n", dwError); #endif
}
void CDataGatherer::SetLastError(DWORD dwError, DWORD dwID) { if (dwError != GATH_ERR_NOERROR) { INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal); if (pInternal) pInternal->m_dwLastError = dwError; }
#ifdef _DEBUG
if (dwError) TRACE2("-- SetLastError(0x%08x, %d)\n", dwError, dwID); #endif
}
//-----------------------------------------------------------------------------
// Sets the error flag based on an HRESULT returned value.
//-----------------------------------------------------------------------------
void CDataGatherer::SetLastErrorHR(HRESULT hrError) { DWORD dwError;
switch (hrError) { case WBEM_E_OUT_OF_MEMORY: dwError = GATH_ERR_NOWBEMOUTOFMEM; break;
case WBEM_E_ACCESS_DENIED: dwError = GATH_ERR_NOWBEMACCESSDENIED; break;
case WBEM_E_INVALID_NAMESPACE: dwError = GATH_ERR_NOWBEMBADSERVER; break;
case WBEM_E_TRANSPORT_FAILURE: dwError = GATH_ERR_NOWBEMNETWORKFAILURE; break;
case WBEM_E_FAILED: case WBEM_E_INVALID_PARAMETER: default: dwError = GATH_ERR_NOWBEMCONNECT; }
SetLastError(dwError); }
void CDataGatherer::SetLastErrorHR(HRESULT hrError, DWORD dwID) { DWORD dwError;
switch (hrError) { case WBEM_E_OUT_OF_MEMORY: dwError = GATH_ERR_NOWBEMOUTOFMEM; break;
case WBEM_E_ACCESS_DENIED: dwError = GATH_ERR_NOWBEMACCESSDENIED; break;
case WBEM_E_INVALID_NAMESPACE: dwError = GATH_ERR_NOWBEMBADSERVER; break;
case WBEM_E_TRANSPORT_FAILURE: dwError = GATH_ERR_NOWBEMNETWORKFAILURE; break;
case WBEM_E_FAILED: case WBEM_E_INVALID_PARAMETER: default: dwError = GATH_ERR_NOWBEMCONNECT; }
SetLastError(dwError, dwID); }
//-----------------------------------------------------------------------------
// Resets the error flag to a no error state (added because SetLastError no
// longer allows this).
//-----------------------------------------------------------------------------
void CDataGatherer::ResetLastError() { m_dwLastError = GATH_ERR_NOERROR; }
//-----------------------------------------------------------------------------
// Return a text representation of the error for display.
//-----------------------------------------------------------------------------
CString CDataGatherer::GetErrorText() { CString strErrorText(_T("")); CString strMachine(m_strDeferredProvider);
AFX_MANAGE_STATE(AfxGetStaticModuleState());
switch (m_dwLastError) { case GATH_ERR_ALLOCATIONFAILED: case GATH_ERR_NOWBEMOUTOFMEM: strErrorText.LoadString(IDS_OUTOFMEMERROR); break; case GATH_ERR_NOWBEMLOCATOR: strErrorText.LoadString(IDS_NOLOCATOR); break; case GATH_ERR_NOWBEMCONNECT: strErrorText.Format(IDS_NOGATHERER, strMachine); break; case GATH_ERR_NOWBEMACCESSDENIED: strErrorText.Format(IDS_GATHERACCESS, strMachine); break; case GATH_ERR_NOWBEMBADSERVER: strErrorText.Format(IDS_BADSERVER, strMachine); break; case GATH_ERR_NOWBEMNETWORKFAILURE: strErrorText.Format(IDS_NETWORKERROR, strMachine); break; default: case GATH_ERR_BADCATEGORYID: strErrorText.LoadString(IDS_UNEXPECTED); break; }
return strErrorText; }
CString CDataGatherer::GetErrorText(DWORD dwID) { CString strErrorText(_T("")); CString strMachine(m_strDeferredProvider);
AFX_MANAGE_STATE(AfxGetStaticModuleState());
INTERNAL_CATEGORY * pInternal = GetInternalRep(dwID); ASSERT(pInternal); if (pInternal) { switch (pInternal->m_dwLastError) { case GATH_ERR_ALLOCATIONFAILED: case GATH_ERR_NOWBEMOUTOFMEM: strErrorText.LoadString(IDS_OUTOFMEMERROR); break; case GATH_ERR_NOWBEMLOCATOR: strErrorText.LoadString(IDS_NOLOCATOR); break; case GATH_ERR_NOWBEMCONNECT: strErrorText.Format(IDS_NOGATHERER, strMachine); break; case GATH_ERR_NOWBEMACCESSDENIED: strErrorText.Format(IDS_GATHERACCESS, strMachine); break; case GATH_ERR_NOWBEMBADSERVER: strErrorText.Format(IDS_BADSERVER, strMachine); break; case GATH_ERR_NOWBEMNETWORKFAILURE: strErrorText.Format(IDS_NETWORKERROR, strMachine); break; default: case GATH_ERR_BADCATEGORYID: strErrorText.LoadString(IDS_UNEXPECTED); break; } }
return strErrorText; }
//-----------------------------------------------------------------------------
// This method is used to construct a CDataCategory object for the passed ID.
// The caller is responsible for ultimately deallocating the object. We use
// the m_mapCategories to retrieve an internal representation of the category,
// construct a CDataCategory object and set it up to refer the this category.
//-----------------------------------------------------------------------------
CDataCategory * CDataGatherer::BuildDataCategory(DWORD dwID) { CDataCategory * pReturnCategory; INTERNAL_CATEGORY * pInternalCat;
ASSERT(m_fInitialized); ASSERT(dwID != 0);
SetLastError(GATH_ERR_NOERROR); if (!m_fInitialized) { SetLastError(GATH_ERR_NOTINITIALIZED); return FALSE; }
// First, try to look up an internal category representation of the category.
if (!m_mapCategories.Lookup((WORD) dwID, (void * &) pInternalCat)) { SetLastError(GATH_ERR_BADCATEGORYID); return NULL; } ASSERT(pInternalCat); if (pInternalCat == NULL) return NULL; // might be that this category was hidden
// Create the object to return (either a CDataCategory or CDataListCategory,
// depending on the information in pInternalCat).
if (pInternalCat->m_fListView) pReturnCategory = (CDataCategory *) new CDataListCategory; else pReturnCategory = new CDataCategory;
if (pReturnCategory == NULL) { SetLastError(GATH_ERR_ALLOCATIONFAILED); return NULL; }
// All the external category theoretically needs is a pointer to this object
// and its ID number.
pReturnCategory->m_pGatherer = this; pReturnCategory->m_dwID = dwID;
return pReturnCategory; }
//-----------------------------------------------------------------------------
// Recursive method used internally to find a category path based on a
// category identifier.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::FindCategoryByIdentifer(const CString & strIdentifier, CString & strPath, DWORD dwID) { INTERNAL_CATEGORY * pInternalCat;
// Look up the internal representation for the specified category.
pInternalCat = GetInternalRep(dwID);
// If this category is the one we're looking for, add the name to the
// path variable and return true.
if (strIdentifier.CompareNoCase(pInternalCat->m_strIdentifier) == 0) { if (!strPath.IsEmpty()) strPath += CString(_T("\\")); strPath += pInternalCat->m_categoryName.m_strText; return TRUE; } // Otherwise, look through the children.
DWORD dwChildID = pInternalCat->m_dwChildID; while (dwChildID) { if (FindCategoryByIdentifer(strIdentifier, strPath, dwChildID)) return TRUE; dwChildID = pInternalCat->m_dwNextID; }
return FALSE; }
//-----------------------------------------------------------------------------
// This method is used to convert a string path (category names, starting at
// the root category, delimited by backslashes) into the ID for the category.
//-----------------------------------------------------------------------------
DWORD CDataGatherer::FindCategoryByPath(const CString & strPath) { INTERNAL_CATEGORY * pInternalCat; CString strWorkingPath(strPath), strNextCategory; DWORD dwID = 0, dwSearchID, dwCurrentID = 0;
while (!strWorkingPath.IsEmpty()) { GetToken(strNextCategory, strWorkingPath, _T('\\'));
// Look for the child of the current category to match the name. If the
// current category ID is zero, make sure the root category name matches.
if (dwCurrentID == 0) { pInternalCat = GetInternalRep(m_dwRootID); if (pInternalCat == NULL || pInternalCat->m_categoryName.m_strText.CompareNoCase(strNextCategory)) return 0; dwCurrentID = m_dwRootID; } else { // Start looking through the children of the current node.
ASSERT(pInternalCat && pInternalCat->m_dwID == dwCurrentID); dwSearchID = pInternalCat->m_dwChildID; while (dwSearchID) { pInternalCat = GetInternalRep(dwSearchID); if (pInternalCat == NULL) return 0;
if (pInternalCat->m_categoryName.m_strText.CompareNoCase(strNextCategory) == 0) break;
dwSearchID = pInternalCat->m_dwNextID; }
if (dwSearchID == 0) return 0; else dwCurrentID = dwSearchID; } } return dwCurrentID; }
//-----------------------------------------------------------------------------
// This method searches the specified category and all of it's children
// for a string. It starts from the iLineth line. If a match is found, the
// line number and path to the category where the match was made are set,
// and we return TRUE.
//-----------------------------------------------------------------------------
BOOL CDataGatherer::RecursiveFind(INTERNAL_CATEGORY * pCat, MSI_FIND_STRUCT *pFind, int & iLine, CString & strPath) { ASSERT(pCat); ASSERT(pFind);
// Look through the lines in the current category for a match with strSearch.
// Note: only look through lines and columns with the appropriate complexity
// (BASIC or ADVANCED). The line number should take this into account as well.
int iResultLine = 0, iCurrentLine = 0; CString strValue;
// A line number of -1 indicates that we should check the category name.
if (iLine == -1) { strValue = pCat->m_categoryName.m_strText; if (!pFind->m_fCaseSensitive) strValue.MakeUpper();
if (strValue.Find(pFind->m_strSearch) != -1) { GetCategoryPath(pCat, strPath); return TRUE; }
iLine = 0; }
// Otherwise, look through lines for this category.
if (pFind->m_fSearchData && pCat->m_fListView) { // We need to search the message which is displayed when there is no
// data as well (searching a saved file does this).
if (pCat->m_dwLineCount == 0) { if (iLine == 0) { strValue = pCat->m_strNoInstances; if (!pFind->m_fCaseSensitive) strValue.MakeUpper();
if (strValue.Find(pFind->m_strSearch) != -1) { iLine = 0; GetCategoryPath(pCat, strPath); return TRUE; } } } else { // This category does have data - search through it.
while (iCurrentLine < (int)pCat->m_dwLineCount) { // Check to see if the Find has been cancelled.
if (pFind->m_pfCancel && *pFind->m_pfCancel) return FALSE;
if (m_complexity == ADVANCED || pCat->m_apLines[iCurrentLine]->m_datacomplexity == BASIC) { // Search through the columns of data for a match. Start looking only after
// we've skipped any lines indicated by the iLine parameter.
if (iResultLine >= iLine) { GATH_FIELD * pCol = pCat->m_pColSpec; int iCurrentCol = 0;
while (pCol) { if (m_complexity == ADVANCED || pCol->m_datacomplexity == BASIC) { strValue = pCat->m_apLines[iCurrentLine]->m_aValue[iCurrentCol].m_strText; if (!pFind->m_fCaseSensitive) strValue.MakeUpper();
if (strValue.Find(pFind->m_strSearch) != -1) { iLine = iResultLine; GetCategoryPath(pCat, strPath); return TRUE; } }
pCol = pCol->m_pNext; iCurrentCol++; } } iResultLine++; } iCurrentLine++; } } }
// No matches were found. Search through the children of this category for a
// match. Create a temporary path and line variable to pass to the children.
CString strTempPath; int iTempLine; INTERNAL_CATEGORY * pChildCat;
DWORD dwChildID = pCat->m_dwChildID; while (dwChildID) { pChildCat = GetInternalRep(dwChildID); if (pChildCat) { iTempLine = (pFind->m_fSearchCategories) ? -1 : 0; if (RecursiveFind(pChildCat, pFind, iTempLine, strTempPath)) { strPath = strTempPath; iLine = iTempLine; return TRUE; }
dwChildID = pChildCat->m_dwNextID; } else dwChildID = 0; }
return FALSE; }
//-----------------------------------------------------------------------------
// Return the path of category names to get from the root category to this
// category, separated by backslashes.
//-----------------------------------------------------------------------------
void CDataGatherer::GetCategoryPath(INTERNAL_CATEGORY * pCat, CString & strPath) { INTERNAL_CATEGORY * pParent; CString strWorking;
pParent = pCat; while (pParent) { if (!strWorking.IsEmpty()) strWorking = CString(_T("\\")) + strWorking; strWorking = pParent->m_categoryName.m_strText + strWorking;
if (pParent->m_dwParentID) pParent = GetInternalRep(pParent->m_dwParentID); else break; } ASSERT(pParent); // if not set we tried to get a ptr for a bad ID, or bad param
strPath = strWorking; }
//-----------------------------------------------------------------------------
// Determine is the passed category pointer is a child category of the passed
// category path. Do this by using the pointer to get a category path, and
// doing a string search for the parent (the parent path will be in any child's
// path).
//-----------------------------------------------------------------------------
BOOL CDataGatherer::IsChildPath(INTERNAL_CATEGORY * pInternalCat, const CString & strParentPath) { CString strParent, strChild;
GetCategoryPath(pInternalCat, strChild); strParent = strParentPath;
strParent.MakeUpper(); strChild.MakeUpper();
return (strChild.Find(strParent) != -1 && strChild.Compare(strParent) != 0); }
//-----------------------------------------------------------------------------
// Dump out the contents of the CDataGatherer object (DEBUG only).
//-----------------------------------------------------------------------------
#ifdef _DEBUG
void CDataGatherer::Dump() { ASSERT(m_fInitialized); if (!m_fInitialized) return;
TRACE0("Dumping CDataGatherer object...\n"); TRACE1(" m_fInitialized = %s\n", (m_fInitialized ? "TRUE" : "FALSE")); TRACE1(" m_dwNextFreeID = %ld\n", m_dwNextFreeID); TRACE1(" m_dwRootID = %ld\n", m_dwRootID);
INTERNAL_CATEGORY * pCat = GetInternalRep(m_dwRootID); if (pCat) pCat->DumpCategoryRecursive(4, this); } #endif
|