|
|
// RotObj.cpp : Implementation of CContentRotator class, which does all the
// interesting work
// George V. Reilly [email protected] [email protected] Nov/Dec 96
// Shortcomings: this works fine for small-to-medium sized tip files
// (under 2000 lines), but it's not very efficient for large ones.
#include "stdafx.h"
#include <new>
#include "ContRot.h"
#include "RotObj.h"
#include "debug.h"
#include <time.h>
#include "Monitor.h"
#define MAX_WEIGHT 10000
#define INVALID_WEIGHT 0xFFFFFFFF
extern CMonitor* g_pMonitor;
//
// forward declaration of some utility functions
//
LPTSTR TcsDup(LPCTSTR ptsz); LPTSTR GetLine(LPTSTR& rptsz); BOOL IsBlankString(LPCTSTR ptsz); UINT GetWeight(LPTSTR& rptsz); LPTSTR GetTipText(LPTSTR& rptsz); HRESULT ReportError(DWORD dwErr); HRESULT ReportError(HRESULT hr);
#if DBG
#define ASSERT_VALID(pObj) \
do {ASSERT(pObj != NULL); pObj->AssertValid();} while (0) #else
#define ASSERT_VALID(pObj) ((void)0)
#endif
class CTipNotify : public CMonitorNotify { public: CTipNotify(); virtual void Notify(); bool IsNotified(); private: long m_isNotified; };
DECLARE_REFPTR( CTipNotify,CMonitorNotify )
CTipNotify::CTipNotify() : m_isNotified(0) { }
void CTipNotify::Notify() { ::InterlockedExchange( &m_isNotified, 1 ); }
bool CTipNotify::IsNotified() { return ( ::InterlockedExchange( &m_isNotified, 0 ) ? true : false ); }
//
// "Tip", as in tip of the day
//
class CTip { public: CTip( LPCTSTR ptszTip, UINT uWeight) : m_ptsz(ptszTip), m_uWeight(uWeight), m_cServingsLeft(uWeight), m_pPrev(NULL), m_pNext(NULL) { ASSERT_VALID(this); } ~CTip() { ASSERT_VALID(this); if (m_pPrev != NULL) m_pPrev->m_pNext = NULL; if (m_pNext != NULL) m_pNext->m_pPrev = NULL; }
#if DBG
void AssertValid() const; #endif
LPCTSTR m_ptsz; // data string
UINT m_uWeight; // weight of this tip, 1 <= m_uWeight <= MAX_WEIGHT
UINT m_cServingsLeft;// how many servings left: no more than m_uWeight
CTip* m_pPrev; // Previous in tips list
CTip* m_pNext; // Next in tips list
};
//
// A list of CTips, which are read from a datafile
//
class CTipList { public: CTipList() : m_ptszFilename(NULL), m_ptszData(NULL), m_cTips(0), m_uTotalWeight(0), m_pTipsListHead(NULL), m_pTipsListTail(NULL), m_fUTF8(false) { m_pNotify = new CTipNotify; ASSERT_VALID(this); }
~CTipList() { ASSERT_VALID(this);
// check for both a valid Filename ptr as well as a valid Monitor ptr.
// If the ContRotModule::Unlock is called prior to this destructor,then
// the Monitor object has already been cleaned up and deleted.
DeleteTips(); ASSERT_VALID(this); }
HRESULT ReadDataFile( LPCTSTR ptszFilename);
HRESULT SameAsCachedFile( LPCTSTR ptszFilename, BOOL& rfIsSame);
UINT Rand() const;
void AppendTip( CTip* pTip);
void RemoveTip( CTip* pTip);
HRESULT DeleteTips();
#if DBG
void AssertValid() const; #endif
LPTSTR m_ptszFilename; // Name of tips file
LPTSTR m_ptszData; // Buffer containing contents of file
UINT m_cTips; // # tips
UINT m_uTotalWeight; // sum of all weights
CTip* m_pTipsListHead; // Head of list of tips
CTip* m_pTipsListTail; // Tail of list of tips
CTipNotifyPtr m_pNotify; bool m_fUTF8; };
//
// A class that allows you to enter a critical section and automatically
// leave when the object of this class goes out of scope. Also provides
// the means to leave and re-enter as needed while protecting against
// entering or leaving out of sync.
//
class CAutoLeaveCritSec { public: CAutoLeaveCritSec( CRITICAL_SECTION* pCS) : m_pCS(pCS), m_fInCritSec(FALSE) {Enter();} ~CAutoLeaveCritSec() {Leave();} // Use this function to re-enter the critical section.
void Enter() {if (!m_fInCritSec) {EnterCriticalSection(m_pCS); m_fInCritSec = TRUE;}}
// Use this function to leave the critical section before going out
// of scope.
void Leave() {if (m_fInCritSec) {LeaveCriticalSection(m_pCS); m_fInCritSec = FALSE;}}
protected: CRITICAL_SECTION* m_pCS; BOOL m_fInCritSec; };
//
// Wrapper class for handles to files opened for reading
//
class CHFile { public: CHFile(LPCTSTR ptszFilename) { m_hFile = ::CreateFile(ptszFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); }
~CHFile() { if (m_hFile != INVALID_HANDLE_VALUE) ::CloseHandle(m_hFile); }
operator HANDLE() const {return m_hFile;}
BOOL operator!() const {return (m_hFile == INVALID_HANDLE_VALUE);}
private: // private, unimplemented default ctor, copy ctor, and op= to prevent
// compiler synthesizing them
CHFile(); CHFile(const CHFile&); CHFile& operator=(const CHFile&);
HANDLE m_hFile; };
/////////////////////////////////////////////////////////////////////////////
// CContentRotator Public Methods
//
// ctor
//
CContentRotator::CContentRotator() : m_ptl(NULL), m_ptlUsed(NULL) { TRACE0("CContentRotator::CContentRotator\n");
InitializeCriticalSection(&m_CS); #if (_WIN32_WINNT >= 0x0403)
SetCriticalSectionSpinCount(&m_CS, 1000); #endif
// Seed the random-number generator with the current time so that
// the numbers will be different each time that we run
::srand((unsigned) time(NULL));
ATLTRY(m_ptl = new CTipList); ATLTRY(m_ptlUsed = new CTipList); }
//
// dtor
//
CContentRotator::~CContentRotator() { TRACE0("CContentRotator::~CContentRotator\n");
DeleteCriticalSection(&m_CS); delete m_ptl; delete m_ptlUsed; }
//
// ATL Wizard generates this
//
STDMETHODIMP CContentRotator::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IContentRotator, };
for (int i=0; i<sizeof(arr)/sizeof(arr[0]); i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; }
//
// Read in the tips in bstrDataFile (a logical name), and return a random
// tip in pbstrRetVal
//
STDMETHODIMP CContentRotator::ChooseContent( BSTR bstrDataFile, BSTR* pbstrRetVal) { HRESULT hr = E_FAIL;
try { // TRACE1("ChooseContent(%ls)\n", bstrDataFile);
if (bstrDataFile == NULL || pbstrRetVal == NULL) return ::ReportError(E_POINTER); else *pbstrRetVal = NULL;
CContext cxt; hr = cxt.Init( CContext::get_Server ); if ( !FAILED(hr) ) { // Do we have valid CTipLists?
if ((m_ptl != NULL) && (m_ptlUsed != NULL)) { // Map bstrDataFile (a logical name such as /controt/tips.txt) to
// a physical filesystem name such as d:\inetpub\controt\tips.txt.
CComBSTR bstrPhysicalDataFile; hr = cxt.Server()->MapPath(bstrDataFile, &bstrPhysicalDataFile);
if (SUCCEEDED(hr)) hr = _ChooseContent(bstrPhysicalDataFile, pbstrRetVal); } else { hr = ::ReportError(E_OUTOFMEMORY); } } else { hr = ::ReportError(E_NOINTERFACE); } } catch ( std::bad_alloc& ) { hr = ::ReportError(E_OUTOFMEMORY); } catch ( ... ) { hr = E_FAIL; } return hr; }
//
// Writes all of the entries in the tip file, each separated by an <hr>, back
// to the user's browser. This can be used to proofread all of the entries.
//
STDMETHODIMP CContentRotator::GetAllContent( BSTR bstrDataFile) { HRESULT hr = E_FAIL; try { if (bstrDataFile == NULL) return ::ReportError(E_POINTER);
CContext cxt; hr = cxt.Init( CContext::get_Server | CContext::get_Response ); // Do we have valid Server and Response objects?
if ( !FAILED( hr ) ) { // Do we have valid CTipLists?
if ( (m_ptl != NULL) && (m_ptlUsed != NULL)) { // Map bstrDataFile (a logical name such as /IISSamples/tips.txt) to
// a physical filesystem name such as d:\inetpub\IISSamples\tips.txt.
CComBSTR bstrPhysicalDataFile; hr = cxt.Server()->MapPath(bstrDataFile, &bstrPhysicalDataFile);
// See note below about critical sections
CAutoLeaveCritSec alcs(&m_CS);
if (SUCCEEDED(hr)) hr = _ReadDataFile(bstrPhysicalDataFile, TRUE);
if (SUCCEEDED(hr)) { const CComVariant cvHR(OLESTR("\n<hr>\n\n")); BOOL bFirstTip = TRUE;
for (CTip* pTip = m_ptl->m_pTipsListHead; pTip != NULL; pTip = pTip->m_pNext) { // Write the leading HR only on the first pass
if (bFirstTip == TRUE) { cxt.Response()->Write(cvHR); bFirstTip = FALSE; }
// Write back to the user's browser, one tip at a time.
// This is more efficient than concatenating all of the
// tips into a potentially huge string and returning that.
CMBCSToWChar convStr; BSTR pbstrTip;
// need to convert the string to Wide based on the UTF8 flag
if (hr = convStr.Init(pTip->m_ptsz, m_ptl->m_fUTF8 ? 65001 : CP_ACP)) { break; }
// make a proper BSTR out of the wide version
if (!(pbstrTip = ::SysAllocString(convStr.GetString()))) { hr = ::ReportError( E_OUTOFMEMORY ); break; }
cxt.Response()->Write(CComVariant(pbstrTip)); cxt.Response()->Write(cvHR);
::SysFreeString(pbstrTip); } } } else { hr = ::ReportError(E_OUTOFMEMORY); }
} else { hr = ::ReportError(E_NOINTERFACE); } } catch ( std::bad_alloc& ) { hr = ::ReportError( E_OUTOFMEMORY ); } catch ( ... ) { hr = E_FAIL; } return hr; }
/////////////////////////////////////////////////////////////////////////////
// CContentRotator Private Methods
//
// Do the work of ChooseContent, but with a real filename, not with a
// virtual filename
//
HRESULT CContentRotator::_ChooseContent( BSTR bstrPhysicalDataFile, BSTR* pbstrRetVal) { ASSERT(bstrPhysicalDataFile != NULL && pbstrRetVal != NULL); // The critical section ensures that the remaining code in this
// function is executing on only one thread at a time. This ensures
// that the cached contents of the tip list are consistent for the
// duration of a call.
// Actually, the critical section is not needed at all. Because we
// need to call Server.MapPath to map the virtual path of
// bstrDataFile to a physical filesystem path, the OnStartPage method
// must be called, as this is the only way that we can get access to
// the ScriptingContext object and thereby the Server object.
// However, the OnStartPage method is only called for page-level
// objects (object is created and destroyed in a single page) and for
// session-level objects. Page-level objects don't have to worry
// about protecting their data from multiple access (unless it's
// global data shared between several objects) and neither do
// session-level objects. Only application-level objects need worry
// about protecting their private data, but application-level objects
// don't give us any way to map the virtual path.
// The Content Rotator might be more useful it it were an
// application-level object. We would get better distribution of the
// tips (see below) and do a lot less rereading of the data file. The
// trivial changes necessary to accept a filesystem path, such as
// "D:\ContRot\tips.txt", instead of a virtual path, such as
// "/IISSamples/tips.txt",are left as an exercise for the reader.
CAutoLeaveCritSec alcs(&m_CS);
HRESULT hr = _ReadDataFile(bstrPhysicalDataFile, FALSE);
if (SUCCEEDED(hr)) { const UINT uRand = m_ptl->Rand(); UINT uCumulativeWeight = 0; CTip* pTip = m_ptl->m_pTipsListHead; LPCTSTR ptszTip = NULL; for ( ; ; ) { ASSERT_VALID(pTip); ptszTip = pTip->m_ptsz; uCumulativeWeight += pTip->m_uWeight;
if (uCumulativeWeight <= uRand) pTip = pTip->m_pNext; else { // Found the tip. Now we go through a bit of work to make
// sure that each tip is served up with the correct
// probability. If the tip has already been served up as
// many times as it's allowed (i.e., m_uWeight times), then
// it's moved to the Used List. Otherwise, it's (probably)
// moved to the end of the Tips List, to reduce the
// likelihood of it turning up too soon again and to
// randomize the order of the tips in the list. When all
// tips have been moved to the Used List, we start afresh.
// If the object is created, used, and destroyed in a
// single page (i.e., it's not a session-level object),
// then none of this does us any good. The list is in
// exactly the same order as it is in the data file and
// we just have to hope that Rand() does give us
// well-distributed random numbers.
// If you expect a single user to see more than one tip,
// you should use a session-level object, to benefit from
// the better distribution of tips. This would be the case
// if you're serving up tips from the same file on multiple
// pages, or if you have a page that automatically
// refreshes itself, such as the one included in the
// Samples directory.
if (--pTip->m_cServingsLeft > 0) { // Move it to the end of the list some of the time.
// If we move it there all of the time, then a heavily
// weighted tip is more likely to turn up a lot
// as the main list nears exhaustion.
if (rand() % 3 == 0) { // TRACE1("Move to End\n%s\n", ptszTip);
m_ptl->RemoveTip(pTip); m_ptl->AppendTip(pTip); } } else { // TRACE1("Move to Used\n%s\n", ptszTip);
pTip->m_cServingsLeft = pTip->m_uWeight; // reset
m_ptl->RemoveTip(pTip); m_ptlUsed->AppendTip(pTip);
if (m_ptl->m_cTips == 0) { TRACE0("List exhausted; swapping\n"); CTipList* const ptlTemp = m_ptl; m_ptl = m_ptlUsed; m_ptlUsed = ptlTemp; } }
break; } }
// TRACE2("total weight = %u, rand = %u\n",
// m_ptl->m_uTotalWeight, uRand);
// TRACE1("tip = `%s'\n", ptszTip);
CMBCSToWChar convStr;
if (hr = convStr.Init(ptszTip, m_ptl->m_fUTF8 ? 65001 : CP_ACP));
else { *pbstrRetVal = ::SysAllocString(convStr.GetString()); } }
return hr; }
HRESULT CContentRotator::_ReadDataFile( BSTR bstrPhysicalDataFile, BOOL fForceReread) { USES_CONVERSION; // needed for OLE2T
LPCTSTR ptszFilename = OLE2T(bstrPhysicalDataFile); HRESULT hr = S_OK;
if (ptszFilename == NULL) { return E_OUTOFMEMORY; }
// Have we cached this tips file already?
if (!fForceReread) { BOOL fIsSame; HRESULT hr = m_ptl->SameAsCachedFile(ptszFilename, fIsSame);
TRACE(_T("%same file\n"), fIsSame ? _T("S") : _T("Not s"));
if (FAILED(hr) || fIsSame) return hr; } // destroy any old tips
m_ptl->DeleteTips(); m_ptlUsed->DeleteTips();
hr = m_ptl->ReadDataFile(ptszFilename);
if (FAILED(hr)) { m_ptl->DeleteTips(); m_ptlUsed->DeleteTips(); }
return hr; }
/////////////////////////////////////////////////////////////////////////////
// CTipList Public Methods
//
// Read a collection of tips from ptszDataFile
//
// The file format is zero or more copies of the following:
// One or more lines starting with "%%"
// Each %% line contains zero or more directives:
// #<weight> (positive integer, 1 <= weight <= MAX_WEIGHT)
// //<comment> (a comment that runs to the end of the line)
// The tip text follows, spread out over several lines
//
HRESULT CTipList::ReadDataFile( LPCTSTR ptszFilename) { TRACE1("ReadDataFile(%s)\n", ptszFilename);
UINT weightSum = 0;
if ( m_ptszFilename != NULL ) { g_pMonitor->StopMonitoringFile( m_ptszFilename ); delete [] m_ptszFilename; }
m_ptszFilename = TcsDup(ptszFilename);
if (m_ptszFilename == NULL) return ::ReportError(E_OUTOFMEMORY);
// Open the file
CHFile hFile(m_ptszFilename);
if (!hFile) return ::ReportError(::GetLastError());
// Get the last-write-time and the filesize
BY_HANDLE_FILE_INFORMATION bhfi;
if (!::GetFileInformationByHandle(hFile, &bhfi)) return ::ReportError(::GetLastError());
// If it's more than 4GB, let's not even think about it!
if (bhfi.nFileSizeHigh != 0) return ::ReportError(E_OUTOFMEMORY);
// Calculate the number of TCHARs in the file
const DWORD cbFile = bhfi.nFileSizeLow; const DWORD ctc = cbFile / sizeof(TCHAR);
// Allocate a buffer for the file's contents
m_ptszData = NULL; ATLTRY(m_ptszData = new TCHAR [ctc + 2]); if (m_ptszData == NULL) return ::ReportError(E_OUTOFMEMORY);
// Read the file into the memory buffer. Let's be paranoid and
// not assume that ReadFile gives us the whole file in one chunk.
DWORD cbSeen = 0;
do { DWORD cbToRead = cbFile - cbSeen; DWORD cbRead = 0;
if (!::ReadFile(hFile, ((LPBYTE) m_ptszData) + cbSeen, cbToRead, &cbRead, NULL)) return ::ReportError(::GetLastError());
cbSeen += cbRead; } while (cbSeen < cbFile);
m_ptszData[ctc] = _T('\0'); // Nul-terminate the string
LPTSTR ptsz = m_ptszData;
#ifdef _UNICODE
#error "This file should NOT be compiled with _UNICODE defined!!!
// Check for byte-order mark
if (*ptsz == 0xFFFE) { // Byte-reversed Unicode file. Swap the hi- and lo-bytes in each wchar
for ( ; ptsz < m_ptszData + ctc; ++ptsz) { BYTE* pb = (BYTE*) ptsz; const BYTE bHi = pb[1]; pb[1] = pb[0]; pb[0] = bHi; } ptsz = m_ptszData; }
if (*ptsz == 0xFEFF) ++ptsz; // skip the byte-order mark
#endif
// check for the UTF-8 BOM
if ((ctc > 3) && (ptsz[0] == (TCHAR)0xef) && (ptsz[1] == (TCHAR)0xbb) && (ptsz[2] == (TCHAR)0xbf)) {
// note its presence and advance the file pointer past it.
m_fUTF8 = true; ptsz += 3; }
// Finally, parse the file
while (ptsz < m_ptszData + ctc) { UINT uWeight = GetWeight(ptsz);
// a value of INVALID_WEIGHT for weight indicates that no weight was found,
// i.e. an invalid data file, or the value was not valid.
if (uWeight == INVALID_WEIGHT) { return ::ReportError((DWORD)ERROR_INVALID_DATA); }
weightSum += uWeight;
if (weightSum > MAX_WEIGHT) { return ::ReportError((DWORD)ERROR_INVALID_DATA); }
LPTSTR ptszTipText = GetTipText(ptsz);
if (!IsBlankString(ptszTipText) && uWeight > 0) { CTip* pTip = NULL; ATLTRY(pTip = new CTip(ptszTipText, uWeight)); if (pTip == NULL) return ::ReportError(E_OUTOFMEMORY); AppendTip(pTip); } else if (ptsz < m_ptszData + ctc) { // not at a terminating "%%" line at the end of the data file
TRACE2("bad tip: tip = `%s', weight = %u\n", ptszTipText, uWeight); } }
g_pMonitor->MonitorFile( m_ptszFilename, m_pNotify );
if (m_uTotalWeight == 0 || m_cTips == 0) return ::ReportError((DWORD)ERROR_INVALID_DATA);
return S_OK; }
//
// Is ptszFilename the same file as m_ptszFilename in both its name
// and timestamp?
//
HRESULT CTipList::SameAsCachedFile( LPCTSTR ptszFilename, BOOL& rfIsSame) { rfIsSame = FALSE; // Have we cached a file at all?
if (m_ptszFilename == NULL) return S_OK; // Are the names the same?
if (_tcsicmp(ptszFilename, m_ptszFilename) != 0) return S_OK;
#if 1
// FILETIME ftLastWriteTime;
// CHFile hFile(ptszFilename);
//
// if (!hFile)
// return ::ReportError(::GetLastError());
//
// if (!::GetFileTime(hFile, NULL, NULL, &ftLastWriteTime))
// return ::ReportError(::GetLastError());
//
// rfIsSame = (::CompareFileTime(&ftLastWriteTime, &m_ftLastWriteTime) == 0);
if ( !m_pNotify->IsNotified() ) { rfIsSame = TRUE; } #else
// The following is more efficient, but it won't work on Win95 with
// Personal Web Server because GetFileAttributesEx is new to NT 4.0.
WIN32_FILE_ATTRIBUTE_DATA wfad;
if (!::GetFileAttributesEx(ptszFilename, GetFileExInfoStandard, (LPVOID) &wfad)) return ::ReportError(::GetLastError());
rfIsSame = (::CompareFileTime(&wfad.ftLastWriteTime, &m_ftLastWriteTime) == 0); #endif
return S_OK; }
//
// Generate a random number in the range 0..m_uTotalWeight-1
//
UINT CTipList::Rand() const { UINT u; ASSERT(m_uTotalWeight > 0); if (m_uTotalWeight == 1) return 0; else if (m_uTotalWeight <= RAND_MAX + 1) u = rand() % m_uTotalWeight; else { // RAND_MAX is only 32,767. This gives us a bigger range
// of random numbers if the weights are large.
u = ((rand() << 15) | rand()) % m_uTotalWeight; } ASSERT(0 <= u && u < m_uTotalWeight); return u; }
//
// Append a tip to the list
//
void CTipList::AppendTip( CTip* pTip) { ASSERT_VALID(this);
pTip->m_pPrev = pTip->m_pNext = NULL; ASSERT_VALID(pTip);
pTip->m_pPrev = m_pTipsListTail;
if (m_pTipsListTail == NULL) m_pTipsListHead = pTip; else m_pTipsListTail->m_pNext = pTip;
m_pTipsListTail = pTip; ++m_cTips; m_uTotalWeight += pTip->m_uWeight;
ASSERT_VALID(this); }
//
// Remove a tip from somewhere in the list
//
void CTipList::RemoveTip( CTip* pTip) { ASSERT_VALID(this); ASSERT_VALID(pTip);
ASSERT(m_cTips > 0);
if (m_cTips == 1) { ASSERT(m_pTipsListHead == pTip && pTip == m_pTipsListTail); m_pTipsListHead = m_pTipsListTail = NULL; } else if (pTip == m_pTipsListHead) { ASSERT(m_pTipsListHead->m_pNext != NULL); m_pTipsListHead = m_pTipsListHead->m_pNext; m_pTipsListHead->m_pPrev = NULL; } else if (pTip == m_pTipsListTail) { ASSERT(m_pTipsListTail->m_pPrev != NULL); m_pTipsListTail = m_pTipsListTail->m_pPrev; m_pTipsListTail->m_pNext = NULL; } else { ASSERT(m_cTips >= 3); pTip->m_pPrev->m_pNext = pTip->m_pNext; pTip->m_pNext->m_pPrev = pTip->m_pPrev; }
pTip->m_pPrev = pTip->m_pNext = NULL; --m_cTips; m_uTotalWeight -= pTip->m_uWeight;
ASSERT_VALID(this); }
//
// Destroy the list of tips and reset all member variables
//
HRESULT CTipList::DeleteTips() { ASSERT_VALID(this);
CTip* pTip = m_pTipsListHead; for (UINT i = 0; i < m_cTips; ++i) { pTip = pTip->m_pNext; delete m_pTipsListHead; m_pTipsListHead = pTip; }
ASSERT(pTip == NULL && m_pTipsListHead == NULL);
// check for both a valid Filename ptr as well as a valid Monitor ptr.
// If the ContRotModule::Unlock is called prior to this destructor,then
// the Monitor object has already been cleaned up and deleted.
if ( (m_ptszFilename != NULL) && (g_pMonitor != NULL) ) { g_pMonitor->StopMonitoringFile( m_ptszFilename ); } delete [] m_ptszFilename; delete [] m_ptszData;
m_ptszFilename = m_ptszData = NULL; // m_ftLastWriteTime.dwLowDateTime = m_ftLastWriteTime.dwHighDateTime = 0;
m_cTips = m_uTotalWeight = 0; m_pTipsListHead = m_pTipsListTail = NULL;
ASSERT_VALID(this);
return S_OK; }
#if DBG
// Paranoia: check that Tips and TipLists are internally consistent.
// Very useful in catching bugs.
void CTip::AssertValid() const { ASSERT(m_ptsz != NULL && m_uWeight > 0); ASSERT(0 < m_cServingsLeft && m_cServingsLeft <= m_uWeight); ASSERT(m_pPrev == NULL || m_pPrev->m_pNext == this); ASSERT(m_pNext == NULL || m_pNext->m_pPrev == this); }
void CTipList::AssertValid() const { if (m_cTips == 0) { ASSERT(m_pTipsListHead == NULL && m_pTipsListTail == NULL); ASSERT(m_uTotalWeight == 0); } else { ASSERT(m_pTipsListHead != NULL && m_pTipsListTail != NULL); ASSERT(m_pTipsListHead->m_pPrev == NULL); ASSERT(m_pTipsListTail->m_pNext == NULL); ASSERT(m_uTotalWeight > 0);
if (m_cTips == 1) ASSERT(m_pTipsListHead == m_pTipsListTail); else ASSERT(m_pTipsListHead != m_pTipsListTail); }
UINT uWeight = 0; CTip* pTip = m_pTipsListHead; UINT i; for (i = 0; i < m_cTips; ++i) { ASSERT_VALID(pTip); uWeight += pTip->m_uWeight;
if (i < m_cTips - 1) pTip = pTip->m_pNext; }
ASSERT(uWeight == m_uTotalWeight); ASSERT(pTip == m_pTipsListTail); }
#endif
/////////////////////////////////////////////////////////////////////////////
// Utility functions
//
// Make a copy of a TSTR that can be deleted with operator delete[]
//
static LPTSTR TcsDup( LPCTSTR ptsz) { LPTSTR ptszNew = NULL; ATLTRY(ptszNew = new TCHAR [_tcslen(ptsz) + 1]); if (ptszNew != NULL) _tcscpy(ptszNew, ptsz); return ptszNew; }
//
// reads a \n-terminated string from rptsz and modifies rptsz to
// point after the end of that string
//
static LPTSTR GetLine( LPTSTR& rptsz) { LPTSTR ptszOrig = rptsz; LPTSTR ptszEol = _tcspbrk(rptsz, _T("\n"));
if (ptszEol != NULL) { // is it "\r\n"?
if (ptszEol > ptszOrig && ptszEol[-1] == _T('\r')) ptszEol[-1] = _T('\0'); else ptszEol[0] = _T('\0');
rptsz = ptszEol + 1; } else { // no newline, so point past the end of the string
rptsz += _tcslen(rptsz); }
// TRACE1("GetLine: `%s'\n", ptszOrig);
return ptszOrig; }
//
// Is the string blank?
//
static BOOL IsBlankString( LPCTSTR ptsz) { if (ptsz == NULL) return TRUE;
while (*ptsz != _T('\0')) if (!_istspace(*ptsz)) return FALSE; else ptsz++;
return TRUE; }
//
// Read a weight line from rptsz and update rptsz to point after the
// end of any %% lines.
//
static UINT GetWeight( LPTSTR& rptsz) { UINT u = INVALID_WEIGHT; // default to invalid weight
while (*rptsz == _T('%')) { LPTSTR ptsz = GetLine(rptsz);
if (ptsz[1] == _T('%')) { u = 1; // now that the format is correct, default to 1
ptsz +=2; // Skip "%%"
while (*ptsz != _T('\0')) { while (_istspace(*ptsz)) ptsz++;
if (*ptsz == _T('/') && ptsz[1] == _T('/')) { // TRACE1("// `%s'\n", ptsz+2);
break; // a comment: ignore the rest of the line
} else if (*ptsz == _T('#')) { ptsz++;
if (_T('0') <= *ptsz && *ptsz <= _T('9')) { LPTSTR ptsz2; u = _tcstoul(ptsz, &ptsz2, 10); ptsz = ptsz2; // TRACE1("#%u\n", u);
if (u > MAX_WEIGHT) u = MAX_WEIGHT; // clamp
} else // ignore word
{ while (*ptsz != _T('\0') && !_istspace(*ptsz)) ptsz++; } } else // ignore word
{ while (*ptsz != _T('\0') && !_istspace(*ptsz)) ptsz++; } } } }
return u; }
//
// Read the multiline tip text. Updates rptsz to point past the end of it.
//
static LPTSTR GetTipText( LPTSTR& rptsz) { LPTSTR ptszOrig = rptsz; LPTSTR ptszEol = _tcsstr(rptsz, _T("\n%%"));
if (ptszEol != NULL) { // is it "\r\n"?
if (ptszEol > rptsz && ptszEol[-1] == _T('\r')) ptszEol[-1] = _T('\0'); else ptszEol[0] = _T('\0');
rptsz = ptszEol + 1; } else { // no "\n%%", so point past the end of the string
rptsz += _tcslen(rptsz); }
// TRACE1("GetTipText: `%s'\n", ptszOrig);
return ptszOrig; }
//
// Set the Error Info. It's up to the calling application to
// decide what to do with it. By default, Denali/VBScript will
// print the error number (and message, if there is one) and
// abort the page.
//
static HRESULT ReportError( HRESULT hr, DWORD dwErr) { HLOCAL pMsgBuf = NULL;
// If there's a message associated with this error, report that
if (::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &pMsgBuf, 0, NULL) > 0) { AtlReportError(CLSID_ContentRotator, (LPCTSTR) pMsgBuf, IID_IContentRotator, hr); }
// TODO: add some error messages to the string resources and
// return those, if FormatMessage doesn't return anything (not
// all system errors have associated error messages).
// Free the buffer, which was allocated by FormatMessage
if (pMsgBuf != NULL) ::LocalFree(pMsgBuf);
return hr; }
//
// Report a Win32 error code
//
static HRESULT ReportError( DWORD dwErr) { return ::ReportError(HRESULT_FROM_WIN32(dwErr), dwErr); }
//
// Report an HRESULT error
//
static HRESULT ReportError( HRESULT hr) { return ::ReportError(hr, (DWORD) hr); }
|