Timestamper Class Extension
TimestampClassExtension.cpp
// Copyright 2006 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.

// TimestampClassExtension.cpp : Implementation of CTimestampClassExtension
#include "stdafx.h"
#include "Timestamper.h"
#include "TimestamperUtils.h"
#include "TimestampClassExtension.h"
#include <windows.h>
#include <oleauto.h>

/////////////////////////////////////////////////////////////////////////////
// CTimestampClassExtension


//////////////////////////////////////////////////////////
// ISupportErrorInfo
STDMETHODIMP CTimestampClassExtension::InterfaceSupportsErrorInfo(REFIID riid)
{
  static const IID* arr[] = 
  {
    &IID_ITimestampClassExtension
  };
  for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    if (InlineIsEqualGUID(*arr[i],riid))
      return S_OK;
  }
  return S_FALSE;
}


//////////////////////////////////////////////////////////
// IClassExtension
STDMETHODIMP CTimestampClassExtension::Init(IClassHelper *pClassHelper, IPropertySet *pExtensionProperties)
{
  HRESULT hr;

  m_ipClassHelper = pClassHelper;

  // initialize user name
  CComBSTR sUserName;
  hr = GetUsrName(&sUserName);
  if (FAILED(hr)) return hr;
  
  m_vUsrName = sUserName;


  // If object class has been just created then, if the default fields are present,
  // use them
  if (pExtensionProperties == NULL)
  {
    hr = TryDefaultProperties();
    if (FAILED(hr)) return hr;
  }
  else
  {
    // Load extension properties into the class variables
    // If the property is not present the class variables
    // are set to an empty string
    CComVariant var;
    hr = pExtensionProperties->GetProperty(g_sCreFieldPropName, &var);
    if (FAILED(hr)) return hr;

    if (var.vt == VT_BSTR) 
      m_sCreFieldName = var.bstrVal; 
    else 
      m_sCreFieldName = L"";

    var.Clear();
    hr = pExtensionProperties->GetProperty(g_sModFieldPropName, &var);
    if (FAILED(hr)) return hr;

    if (var.vt == VT_BSTR) 
      m_sModFieldName = var.bstrVal; 
    else 
      m_sModFieldName = L"";

    var.Clear();
    hr = pExtensionProperties->GetProperty(g_sUsrFieldPropName, &var);
    if (FAILED(hr)) return hr;

    if (var.vt == VT_BSTR) 
      m_sUsrFieldName = var.bstrVal; 
    else 
      m_sUsrFieldName = L"";

  }

  // Check that the required fields are there
  hr = GetFieldPositions();
  if (FAILED(hr)) return hr;
  
  return S_OK;
}

STDMETHODIMP CTimestampClassExtension::Shutdown()
{
  return S_OK;
}

//////////////////////////////////////////////////////////
// IObjectClassEvents
STDMETHODIMP CTimestampClassExtension::OnCreate(IObject *obj)
{
  HRESULT hr;
  // Set the creation date and user name
  // NOTE: there is no need to call IRow::Store
  //
  // For Enterprise geodatabases, it is preferable to use the database
  // date and username, but for simplicity this sample will just use the
  // client OS date and username.

  
  if (m_sCreFieldName.Length() > 0)
  {
    DATE dtimestamp;
    hr = GetTimestamp(&dtimestamp);
    if (FAILED(hr)) return hr;
    
    CComVariant vTimestamp;
    vTimestamp.date = dtimestamp;
    vTimestamp.vt = VT_DATE;

    hr = obj->put_Value(m_lCreField, vTimestamp);
    if (FAILED(hr)) return hr;
  }

  if (m_sUsrFieldName.Length() > 0)
  {
    hr = obj->put_Value(m_lUsrField, m_vUsrName);
    if (FAILED(hr)) return hr;
  }

  return S_OK;
}

STDMETHODIMP CTimestampClassExtension::OnChange(IObject *obj)
{
  HRESULT hr;
  // Set the modification date and user name
  // NOTE: there is no need to call IRow::Store
  //
  // For Enterprise geodatabases, it is preferable to use the database
  // date and username, but for simplicity this sample will just use the
  // client OS date and username.

  if (m_sModFieldName.Length() > 0)
  {
    DATE dtimestamp;
    hr = GetTimestamp(&dtimestamp);
    if (FAILED(hr)) return hr;
    
    CComVariant vTimestamp;
    vTimestamp.date = dtimestamp;
    vTimestamp.vt = VT_DATE;

    hr = obj->put_Value(m_lModField, vTimestamp);
    if (FAILED(hr)) return hr;
  }

  if (m_sUsrFieldName.Length() > 0)
  {
    hr = obj->put_Value(m_lUsrField, m_vUsrName);
    if (FAILED(hr)) return hr;
  }

  return S_OK;

}

STDMETHODIMP CTimestampClassExtension::OnDelete(IObject *obj)
{
  return S_OK;
}


HRESULT CTimestampClassExtension::TryDefaultProperties()
{
  // If any of the default fields are present then
  // put them to an 'in-use' state and update the
  // extension properties accordingly.
  // No need for a schema lock as this is guaranteed to run straight after the
  // object class is created (if no extension properties are provided).
  HRESULT hr;

  IClassPtr ipClass;
  hr = m_ipClassHelper->get_Class(&ipClass);
  if (FAILED(hr)) return hr;

  long lFieldIndex;

  hr = ipClass->FindField(g_sCreFieldDefaultName, &lFieldIndex);
  if (FAILED(hr)) return hr;
  if (lFieldIndex != -1) 
    m_sCreFieldName = g_sCreFieldDefaultName.Copy();

  hr = ipClass->FindField(g_sModFieldDefaultName, &lFieldIndex);
  if (FAILED(hr)) return hr;
  if (lFieldIndex != -1) 
    m_sModFieldName = g_sModFieldDefaultName.Copy();

  hr = ipClass->FindField(g_sUsrFieldDefaultName, &lFieldIndex);
  if (FAILED(hr)) return hr;
  if (lFieldIndex != -1) 
    m_sUsrFieldName = g_sUsrFieldDefaultName.Copy();

  if (m_sCreFieldName.Length() > 0 
   || m_sModFieldName.Length() > 0 
   || m_sUsrFieldName.Length() > 0)
  {
    hr = UpdateProperties();
    if (FAILED(hr)) return hr;
  }

  return S_OK;
}

HRESULT CTimestampClassExtension::GetFieldPositions()
{
  HRESULT hr;

  IClassPtr ipClass;
  hr = m_ipClassHelper->get_Class(&ipClass);
  if (FAILED(hr)) return hr;

  if (m_sCreFieldName.Length() > 0)
  {
    hr = ipClass->FindField(m_sCreFieldName, &m_lCreField);
    if (FAILED(hr)) return hr;
    if (m_lCreField == -1) 
    {
      CComBSTR sError(L"Creation timestamp field not found: ");
      sError.Append(m_sCreFieldName);
      AtlReportError(CLSID_TimestampClassExtension, sError, IID_ITimestampClassExtension, E_FAIL);
      return E_FAIL;
    }
  }
  else
    m_lCreField = -1;


  if (m_sModFieldName.Length() > 0)
  {
    hr = ipClass->FindField(m_sModFieldName, &m_lModField);
    if (FAILED(hr)) return hr;
    if (m_lModField == -1)         
    {
      CComBSTR sError(L"Modification timestamp field not found: ");
      sError.Append(m_sModFieldName);
      AtlReportError(CLSID_TimestampClassExtension, sError, IID_ITimestampClassExtension, E_FAIL);
      return E_FAIL;
    }
  }
  else
    m_lModField = -1;

  if (m_sUsrFieldName.Length() > 0)
  {
    hr = ipClass->FindField(m_sUsrFieldName, &m_lUsrField);
    if (FAILED(hr)) return hr;
    if (m_lUsrField == -1)   
    {
      CComBSTR sError(L"User timestamp field not found: ");
      sError.Append(m_sUsrFieldName);
      AtlReportError(CLSID_TimestampClassExtension, sError, IID_ITimestampClassExtension, E_FAIL);
      return E_FAIL;
    }
  }
  else
    m_lUsrField = -1;

  return S_OK;
}

HRESULT CTimestampClassExtension::GetTimestamp(DATE *dtimestamp)
{

  SYSTEMTIME stimestamp;
  ::GetLocalTime(&stimestamp);

  int success;
  success = ::SystemTimeToVariantTime(&stimestamp, dtimestamp);
  if (!success) return E_FAIL;

  return S_OK;
}

HRESULT CTimestampClassExtension::GetUsrName(BSTR *sUserName)
{
  USES_CONVERSION;
  TCHAR buf[101];
  LPTSTR tstr;
  DWORD cchBuff = 100;       

  tstr = buf;
  ::GetUserName(tstr, &cchBuff);
  
  *sUserName = T2BSTR(tstr);

  return S_OK;
}


//////////////////////////////////////////////////////////
// ITimeStampClassExtension

STDMETHODIMP CTimestampClassExtension::get_CreationFieldName(BSTR *pVal)
{
  if (!pVal)
    return E_POINTER;

  return m_sCreFieldName.CopyTo(pVal);
}

STDMETHODIMP CTimestampClassExtension::put_CreationFieldName(BSTR newVal)
{
  m_sCreFieldName = newVal;
  return S_OK;
}

STDMETHODIMP CTimestampClassExtension::get_ModificationFieldName(BSTR *pVal)
{
  if (!pVal)
    return E_POINTER;

  return m_sModFieldName.CopyTo(pVal);
}

STDMETHODIMP CTimestampClassExtension::put_ModificationFieldName(BSTR newVal)
{
  m_sModFieldName = newVal;
  return S_OK;
}

STDMETHODIMP CTimestampClassExtension::get_UserFieldName(BSTR *pVal)
{
  if (!pVal)
    return E_POINTER;

  return m_sUsrFieldName.CopyTo(pVal);
}

STDMETHODIMP CTimestampClassExtension::put_UserFieldName(BSTR newVal)
{
  m_sUsrFieldName = newVal;
  return S_OK;
}

STDMETHODIMP CTimestampClassExtension::UpdateProperties()
{

  // Note that user ought to have an exclusive schema lock
  // before calling this method
  
  HRESULT hr;

  // Check if the specified fields exist
  hr = GetFieldPositions();
  if (FAILED(hr)) return hr;

  // Make the property set
  IPropertySetPtr ipPropSet(CLSID_PropertySet);
  
  CComVariant vFieldName(m_sCreFieldName);
  hr = ipPropSet->SetProperty(g_sCreFieldPropName, vFieldName);
  if (FAILED(hr)) return hr;

  vFieldName.Clear();
  vFieldName = m_sModFieldName;
  hr = ipPropSet->SetProperty(g_sModFieldPropName, vFieldName);
  if (FAILED(hr)) return hr;

  vFieldName.Clear();
  vFieldName = m_sUsrFieldName;
  hr = ipPropSet->SetProperty(g_sUsrFieldPropName, vFieldName);
  if (FAILED(hr)) return hr;
  
  // Update the schema
  IClassPtr ipClass;
  hr = m_ipClassHelper->get_Class(&ipClass);
  if (FAILED(hr)) return hr;

  IClassSchemaEdit2Ptr ipClassSchemaEdit(ipClass);
  hr = ipClassSchemaEdit->AlterClassExtensionProperties(ipPropSet);
  if (FAILED(hr)) return hr;

  return S_OK;
}