Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1317 lines
33 KiB

  1. // RotObj.cpp : Implementation of CContentRotator class, which does all the
  2. // interesting work
  3. // George V. Reilly [email protected] [email protected] Nov/Dec 96
  4. // Shortcomings: this works fine for small-to-medium sized tip files
  5. // (under 2000 lines), but it's not very efficient for large ones.
  6. #include "stdafx.h"
  7. #include <new>
  8. #include "ContRot.h"
  9. #include "RotObj.h"
  10. #include "debug.h"
  11. #include <time.h>
  12. #include "Monitor.h"
  13. #define MAX_WEIGHT 10000
  14. #define INVALID_WEIGHT 0xFFFFFFFF
  15. extern CMonitor* g_pMonitor;
  16. //
  17. // forward declaration of some utility functions
  18. //
  19. LPTSTR TcsDup(LPCTSTR ptsz);
  20. LPTSTR GetLine(LPTSTR& rptsz);
  21. BOOL IsBlankString(LPCTSTR ptsz);
  22. UINT GetWeight(LPTSTR& rptsz);
  23. LPTSTR GetTipText(LPTSTR& rptsz);
  24. HRESULT ReportError(DWORD dwErr);
  25. HRESULT ReportError(HRESULT hr);
  26. #if DBG
  27. #define ASSERT_VALID(pObj) \
  28. do {ASSERT(pObj != NULL); pObj->AssertValid();} while (0)
  29. #else
  30. #define ASSERT_VALID(pObj) ((void)0)
  31. #endif
  32. class CTipNotify : public CMonitorNotify
  33. {
  34. public:
  35. CTipNotify();
  36. virtual void Notify();
  37. bool IsNotified();
  38. private:
  39. long m_isNotified;
  40. };
  41. DECLARE_REFPTR( CTipNotify,CMonitorNotify )
  42. CTipNotify::CTipNotify()
  43. : m_isNotified(0)
  44. {
  45. }
  46. void
  47. CTipNotify::Notify()
  48. {
  49. ::InterlockedExchange( &m_isNotified, 1 );
  50. }
  51. bool
  52. CTipNotify::IsNotified()
  53. {
  54. return ( ::InterlockedExchange( &m_isNotified, 0 ) ? true : false );
  55. }
  56. //
  57. // "Tip", as in tip of the day
  58. //
  59. class CTip
  60. {
  61. public:
  62. CTip(
  63. LPCTSTR ptszTip,
  64. UINT uWeight)
  65. : m_ptsz(ptszTip),
  66. m_uWeight(uWeight),
  67. m_cServingsLeft(uWeight),
  68. m_pPrev(NULL),
  69. m_pNext(NULL)
  70. {
  71. ASSERT_VALID(this);
  72. }
  73. ~CTip()
  74. {
  75. ASSERT_VALID(this);
  76. if (m_pPrev != NULL)
  77. m_pPrev->m_pNext = NULL;
  78. if (m_pNext != NULL)
  79. m_pNext->m_pPrev = NULL;
  80. }
  81. #if DBG
  82. void
  83. AssertValid() const;
  84. #endif
  85. LPCTSTR m_ptsz; // data string
  86. UINT m_uWeight; // weight of this tip, 1 <= m_uWeight <= MAX_WEIGHT
  87. UINT m_cServingsLeft;// how many servings left: no more than m_uWeight
  88. CTip* m_pPrev; // Previous in tips list
  89. CTip* m_pNext; // Next in tips list
  90. };
  91. //
  92. // A list of CTips, which are read from a datafile
  93. //
  94. class CTipList
  95. {
  96. public:
  97. CTipList()
  98. : m_ptszFilename(NULL),
  99. m_ptszData(NULL),
  100. m_cTips(0),
  101. m_uTotalWeight(0),
  102. m_pTipsListHead(NULL),
  103. m_pTipsListTail(NULL),
  104. m_fUTF8(false)
  105. {
  106. m_pNotify = new CTipNotify;
  107. ASSERT_VALID(this);
  108. }
  109. ~CTipList()
  110. {
  111. ASSERT_VALID(this);
  112. // check for both a valid Filename ptr as well as a valid Monitor ptr.
  113. // If the ContRotModule::Unlock is called prior to this destructor,then
  114. // the Monitor object has already been cleaned up and deleted.
  115. DeleteTips();
  116. ASSERT_VALID(this);
  117. }
  118. HRESULT
  119. ReadDataFile(
  120. LPCTSTR ptszFilename);
  121. HRESULT
  122. SameAsCachedFile(
  123. LPCTSTR ptszFilename,
  124. BOOL& rfIsSame);
  125. UINT
  126. Rand() const;
  127. void
  128. AppendTip(
  129. CTip* pTip);
  130. void
  131. RemoveTip(
  132. CTip* pTip);
  133. HRESULT
  134. DeleteTips();
  135. #if DBG
  136. void
  137. AssertValid() const;
  138. #endif
  139. LPTSTR m_ptszFilename; // Name of tips file
  140. LPTSTR m_ptszData; // Buffer containing contents of file
  141. UINT m_cTips; // # tips
  142. UINT m_uTotalWeight; // sum of all weights
  143. CTip* m_pTipsListHead; // Head of list of tips
  144. CTip* m_pTipsListTail; // Tail of list of tips
  145. CTipNotifyPtr m_pNotify;
  146. bool m_fUTF8;
  147. };
  148. //
  149. // A class that allows you to enter a critical section and automatically
  150. // leave when the object of this class goes out of scope. Also provides
  151. // the means to leave and re-enter as needed while protecting against
  152. // entering or leaving out of sync.
  153. //
  154. class CAutoLeaveCritSec
  155. {
  156. public:
  157. CAutoLeaveCritSec(
  158. CRITICAL_SECTION* pCS)
  159. : m_pCS(pCS), m_fInCritSec(FALSE)
  160. {Enter();}
  161. ~CAutoLeaveCritSec()
  162. {Leave();}
  163. // Use this function to re-enter the critical section.
  164. void Enter()
  165. {if (!m_fInCritSec) {EnterCriticalSection(m_pCS); m_fInCritSec = TRUE;}}
  166. // Use this function to leave the critical section before going out
  167. // of scope.
  168. void Leave()
  169. {if (m_fInCritSec) {LeaveCriticalSection(m_pCS); m_fInCritSec = FALSE;}}
  170. protected:
  171. CRITICAL_SECTION* m_pCS;
  172. BOOL m_fInCritSec;
  173. };
  174. //
  175. // Wrapper class for handles to files opened for reading
  176. //
  177. class CHFile
  178. {
  179. public:
  180. CHFile(LPCTSTR ptszFilename)
  181. {
  182. m_hFile = ::CreateFile(ptszFilename, GENERIC_READ, FILE_SHARE_READ,
  183. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
  184. NULL);
  185. }
  186. ~CHFile()
  187. {
  188. if (m_hFile != INVALID_HANDLE_VALUE)
  189. ::CloseHandle(m_hFile);
  190. }
  191. operator HANDLE() const
  192. {return m_hFile;}
  193. BOOL
  194. operator!() const
  195. {return (m_hFile == INVALID_HANDLE_VALUE);}
  196. private:
  197. // private, unimplemented default ctor, copy ctor, and op= to prevent
  198. // compiler synthesizing them
  199. CHFile();
  200. CHFile(const CHFile&);
  201. CHFile& operator=(const CHFile&);
  202. HANDLE m_hFile;
  203. };
  204. /////////////////////////////////////////////////////////////////////////////
  205. // CContentRotator Public Methods
  206. //
  207. // ctor
  208. //
  209. CContentRotator::CContentRotator()
  210. : m_ptl(NULL),
  211. m_ptlUsed(NULL)
  212. {
  213. TRACE0("CContentRotator::CContentRotator\n");
  214. InitializeCriticalSection(&m_CS);
  215. #if (_WIN32_WINNT >= 0x0403)
  216. SetCriticalSectionSpinCount(&m_CS, 1000);
  217. #endif
  218. // Seed the random-number generator with the current time so that
  219. // the numbers will be different each time that we run
  220. ::srand((unsigned) time(NULL));
  221. ATLTRY(m_ptl = new CTipList);
  222. ATLTRY(m_ptlUsed = new CTipList);
  223. }
  224. //
  225. // dtor
  226. //
  227. CContentRotator::~CContentRotator()
  228. {
  229. TRACE0("CContentRotator::~CContentRotator\n");
  230. DeleteCriticalSection(&m_CS);
  231. delete m_ptl;
  232. delete m_ptlUsed;
  233. }
  234. //
  235. // ATL Wizard generates this
  236. //
  237. STDMETHODIMP CContentRotator::InterfaceSupportsErrorInfo(REFIID riid)
  238. {
  239. static const IID* arr[] =
  240. {
  241. &IID_IContentRotator,
  242. };
  243. for (int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
  244. {
  245. if (InlineIsEqualGUID(*arr[i],riid))
  246. return S_OK;
  247. }
  248. return S_FALSE;
  249. }
  250. //
  251. // Read in the tips in bstrDataFile (a logical name), and return a random
  252. // tip in pbstrRetVal
  253. //
  254. STDMETHODIMP
  255. CContentRotator::ChooseContent(
  256. BSTR bstrDataFile,
  257. BSTR* pbstrRetVal)
  258. {
  259. HRESULT hr = E_FAIL;
  260. try
  261. {
  262. // TRACE1("ChooseContent(%ls)\n", bstrDataFile);
  263. if (bstrDataFile == NULL || pbstrRetVal == NULL)
  264. return ::ReportError(E_POINTER);
  265. else
  266. *pbstrRetVal = NULL;
  267. CContext cxt;
  268. hr = cxt.Init( CContext::get_Server );
  269. if ( !FAILED(hr) )
  270. {
  271. // Do we have valid CTipLists?
  272. if ((m_ptl != NULL) && (m_ptlUsed != NULL))
  273. {
  274. // Map bstrDataFile (a logical name such as /controt/tips.txt) to
  275. // a physical filesystem name such as d:\inetpub\controt\tips.txt.
  276. CComBSTR bstrPhysicalDataFile;
  277. hr = cxt.Server()->MapPath(bstrDataFile, &bstrPhysicalDataFile);
  278. if (SUCCEEDED(hr))
  279. hr = _ChooseContent(bstrPhysicalDataFile, pbstrRetVal);
  280. }
  281. else
  282. {
  283. hr = ::ReportError(E_OUTOFMEMORY);
  284. }
  285. }
  286. else
  287. {
  288. hr = ::ReportError(E_NOINTERFACE);
  289. }
  290. }
  291. catch ( std::bad_alloc& )
  292. {
  293. hr = ::ReportError(E_OUTOFMEMORY);
  294. }
  295. catch ( ... )
  296. {
  297. hr = E_FAIL;
  298. }
  299. return hr;
  300. }
  301. //
  302. // Writes all of the entries in the tip file, each separated by an <hr>, back
  303. // to the user's browser. This can be used to proofread all of the entries.
  304. //
  305. STDMETHODIMP
  306. CContentRotator::GetAllContent(
  307. BSTR bstrDataFile)
  308. {
  309. HRESULT hr = E_FAIL;
  310. try
  311. {
  312. if (bstrDataFile == NULL)
  313. return ::ReportError(E_POINTER);
  314. CContext cxt;
  315. hr = cxt.Init( CContext::get_Server | CContext::get_Response );
  316. // Do we have valid Server and Response objects?
  317. if ( !FAILED( hr ) )
  318. {
  319. // Do we have valid CTipLists?
  320. if ( (m_ptl != NULL) && (m_ptlUsed != NULL))
  321. {
  322. // Map bstrDataFile (a logical name such as /IISSamples/tips.txt) to
  323. // a physical filesystem name such as d:\inetpub\IISSamples\tips.txt.
  324. CComBSTR bstrPhysicalDataFile;
  325. hr = cxt.Server()->MapPath(bstrDataFile, &bstrPhysicalDataFile);
  326. // See note below about critical sections
  327. CAutoLeaveCritSec alcs(&m_CS);
  328. if (SUCCEEDED(hr))
  329. hr = _ReadDataFile(bstrPhysicalDataFile, TRUE);
  330. if (SUCCEEDED(hr))
  331. {
  332. const CComVariant cvHR(OLESTR("\n<hr>\n\n"));
  333. BOOL bFirstTip = TRUE;
  334. for (CTip* pTip = m_ptl->m_pTipsListHead;
  335. pTip != NULL;
  336. pTip = pTip->m_pNext)
  337. {
  338. // Write the leading HR only on the first pass
  339. if (bFirstTip == TRUE) {
  340. cxt.Response()->Write(cvHR);
  341. bFirstTip = FALSE;
  342. }
  343. // Write back to the user's browser, one tip at a time.
  344. // This is more efficient than concatenating all of the
  345. // tips into a potentially huge string and returning that.
  346. CMBCSToWChar convStr;
  347. BSTR pbstrTip;
  348. // need to convert the string to Wide based on the UTF8 flag
  349. if (hr = convStr.Init(pTip->m_ptsz, m_ptl->m_fUTF8 ? 65001 : CP_ACP)) {
  350. break;
  351. }
  352. // make a proper BSTR out of the wide version
  353. if (!(pbstrTip = ::SysAllocString(convStr.GetString()))) {
  354. hr = ::ReportError( E_OUTOFMEMORY );
  355. break;
  356. }
  357. cxt.Response()->Write(CComVariant(pbstrTip));
  358. cxt.Response()->Write(cvHR);
  359. ::SysFreeString(pbstrTip);
  360. }
  361. }
  362. }
  363. else
  364. {
  365. hr = ::ReportError(E_OUTOFMEMORY);
  366. }
  367. }
  368. else
  369. {
  370. hr = ::ReportError(E_NOINTERFACE);
  371. }
  372. }
  373. catch ( std::bad_alloc& )
  374. {
  375. hr = ::ReportError( E_OUTOFMEMORY );
  376. }
  377. catch ( ... )
  378. {
  379. hr = E_FAIL;
  380. }
  381. return hr;
  382. }
  383. /////////////////////////////////////////////////////////////////////////////
  384. // CContentRotator Private Methods
  385. //
  386. // Do the work of ChooseContent, but with a real filename, not with a
  387. // virtual filename
  388. //
  389. HRESULT
  390. CContentRotator::_ChooseContent(
  391. BSTR bstrPhysicalDataFile,
  392. BSTR* pbstrRetVal)
  393. {
  394. ASSERT(bstrPhysicalDataFile != NULL && pbstrRetVal != NULL);
  395. // The critical section ensures that the remaining code in this
  396. // function is executing on only one thread at a time. This ensures
  397. // that the cached contents of the tip list are consistent for the
  398. // duration of a call.
  399. // Actually, the critical section is not needed at all. Because we
  400. // need to call Server.MapPath to map the virtual path of
  401. // bstrDataFile to a physical filesystem path, the OnStartPage method
  402. // must be called, as this is the only way that we can get access to
  403. // the ScriptingContext object and thereby the Server object.
  404. // However, the OnStartPage method is only called for page-level
  405. // objects (object is created and destroyed in a single page) and for
  406. // session-level objects. Page-level objects don't have to worry
  407. // about protecting their data from multiple access (unless it's
  408. // global data shared between several objects) and neither do
  409. // session-level objects. Only application-level objects need worry
  410. // about protecting their private data, but application-level objects
  411. // don't give us any way to map the virtual path.
  412. // The Content Rotator might be more useful it it were an
  413. // application-level object. We would get better distribution of the
  414. // tips (see below) and do a lot less rereading of the data file. The
  415. // trivial changes necessary to accept a filesystem path, such as
  416. // "D:\ContRot\tips.txt", instead of a virtual path, such as
  417. // "/IISSamples/tips.txt",are left as an exercise for the reader.
  418. CAutoLeaveCritSec alcs(&m_CS);
  419. HRESULT hr = _ReadDataFile(bstrPhysicalDataFile, FALSE);
  420. if (SUCCEEDED(hr))
  421. {
  422. const UINT uRand = m_ptl->Rand();
  423. UINT uCumulativeWeight = 0;
  424. CTip* pTip = m_ptl->m_pTipsListHead;
  425. LPCTSTR ptszTip = NULL;
  426. for ( ; ; )
  427. {
  428. ASSERT_VALID(pTip);
  429. ptszTip = pTip->m_ptsz;
  430. uCumulativeWeight += pTip->m_uWeight;
  431. if (uCumulativeWeight <= uRand)
  432. pTip = pTip->m_pNext;
  433. else
  434. {
  435. // Found the tip. Now we go through a bit of work to make
  436. // sure that each tip is served up with the correct
  437. // probability. If the tip has already been served up as
  438. // many times as it's allowed (i.e., m_uWeight times), then
  439. // it's moved to the Used List. Otherwise, it's (probably)
  440. // moved to the end of the Tips List, to reduce the
  441. // likelihood of it turning up too soon again and to
  442. // randomize the order of the tips in the list. When all
  443. // tips have been moved to the Used List, we start afresh.
  444. // If the object is created, used, and destroyed in a
  445. // single page (i.e., it's not a session-level object),
  446. // then none of this does us any good. The list is in
  447. // exactly the same order as it is in the data file and
  448. // we just have to hope that Rand() does give us
  449. // well-distributed random numbers.
  450. // If you expect a single user to see more than one tip,
  451. // you should use a session-level object, to benefit from
  452. // the better distribution of tips. This would be the case
  453. // if you're serving up tips from the same file on multiple
  454. // pages, or if you have a page that automatically
  455. // refreshes itself, such as the one included in the
  456. // Samples directory.
  457. if (--pTip->m_cServingsLeft > 0)
  458. {
  459. // Move it to the end of the list some of the time.
  460. // If we move it there all of the time, then a heavily
  461. // weighted tip is more likely to turn up a lot
  462. // as the main list nears exhaustion.
  463. if (rand() % 3 == 0)
  464. {
  465. // TRACE1("Move to End\n%s\n", ptszTip);
  466. m_ptl->RemoveTip(pTip);
  467. m_ptl->AppendTip(pTip);
  468. }
  469. }
  470. else
  471. {
  472. // TRACE1("Move to Used\n%s\n", ptszTip);
  473. pTip->m_cServingsLeft = pTip->m_uWeight; // reset
  474. m_ptl->RemoveTip(pTip);
  475. m_ptlUsed->AppendTip(pTip);
  476. if (m_ptl->m_cTips == 0)
  477. {
  478. TRACE0("List exhausted; swapping\n");
  479. CTipList* const ptlTemp = m_ptl;
  480. m_ptl = m_ptlUsed;
  481. m_ptlUsed = ptlTemp;
  482. }
  483. }
  484. break;
  485. }
  486. }
  487. // TRACE2("total weight = %u, rand = %u\n",
  488. // m_ptl->m_uTotalWeight, uRand);
  489. // TRACE1("tip = `%s'\n", ptszTip);
  490. CMBCSToWChar convStr;
  491. if (hr = convStr.Init(ptszTip, m_ptl->m_fUTF8 ? 65001 : CP_ACP));
  492. else {
  493. *pbstrRetVal = ::SysAllocString(convStr.GetString());
  494. }
  495. }
  496. return hr;
  497. }
  498. HRESULT
  499. CContentRotator::_ReadDataFile(
  500. BSTR bstrPhysicalDataFile,
  501. BOOL fForceReread)
  502. {
  503. USES_CONVERSION; // needed for OLE2T
  504. LPCTSTR ptszFilename = OLE2T(bstrPhysicalDataFile);
  505. HRESULT hr = S_OK;
  506. if (ptszFilename == NULL) {
  507. return E_OUTOFMEMORY;
  508. }
  509. // Have we cached this tips file already?
  510. if (!fForceReread)
  511. {
  512. BOOL fIsSame;
  513. HRESULT hr = m_ptl->SameAsCachedFile(ptszFilename, fIsSame);
  514. TRACE(_T("%same file\n"), fIsSame ? _T("S") : _T("Not s"));
  515. if (FAILED(hr) || fIsSame)
  516. return hr;
  517. }
  518. // destroy any old tips
  519. m_ptl->DeleteTips();
  520. m_ptlUsed->DeleteTips();
  521. hr = m_ptl->ReadDataFile(ptszFilename);
  522. if (FAILED(hr)) {
  523. m_ptl->DeleteTips();
  524. m_ptlUsed->DeleteTips();
  525. }
  526. return hr;
  527. }
  528. /////////////////////////////////////////////////////////////////////////////
  529. // CTipList Public Methods
  530. //
  531. // Read a collection of tips from ptszDataFile
  532. //
  533. // The file format is zero or more copies of the following:
  534. // One or more lines starting with "%%"
  535. // Each %% line contains zero or more directives:
  536. // #<weight> (positive integer, 1 <= weight <= MAX_WEIGHT)
  537. // //<comment> (a comment that runs to the end of the line)
  538. // The tip text follows, spread out over several lines
  539. //
  540. HRESULT
  541. CTipList::ReadDataFile(
  542. LPCTSTR ptszFilename)
  543. {
  544. TRACE1("ReadDataFile(%s)\n", ptszFilename);
  545. UINT weightSum = 0;
  546. if ( m_ptszFilename != NULL )
  547. {
  548. g_pMonitor->StopMonitoringFile( m_ptszFilename );
  549. delete [] m_ptszFilename;
  550. }
  551. m_ptszFilename = TcsDup(ptszFilename);
  552. if (m_ptszFilename == NULL)
  553. return ::ReportError(E_OUTOFMEMORY);
  554. // Open the file
  555. CHFile hFile(m_ptszFilename);
  556. if (!hFile)
  557. return ::ReportError(::GetLastError());
  558. // Get the last-write-time and the filesize
  559. BY_HANDLE_FILE_INFORMATION bhfi;
  560. if (!::GetFileInformationByHandle(hFile, &bhfi))
  561. return ::ReportError(::GetLastError());
  562. // If it's more than 4GB, let's not even think about it!
  563. if (bhfi.nFileSizeHigh != 0)
  564. return ::ReportError(E_OUTOFMEMORY);
  565. // Calculate the number of TCHARs in the file
  566. const DWORD cbFile = bhfi.nFileSizeLow;
  567. const DWORD ctc = cbFile / sizeof(TCHAR);
  568. // Allocate a buffer for the file's contents
  569. m_ptszData = NULL;
  570. ATLTRY(m_ptszData = new TCHAR [ctc + 2]);
  571. if (m_ptszData == NULL)
  572. return ::ReportError(E_OUTOFMEMORY);
  573. // Read the file into the memory buffer. Let's be paranoid and
  574. // not assume that ReadFile gives us the whole file in one chunk.
  575. DWORD cbSeen = 0;
  576. do
  577. {
  578. DWORD cbToRead = cbFile - cbSeen;
  579. DWORD cbRead = 0;
  580. if (!::ReadFile(hFile, ((LPBYTE) m_ptszData) + cbSeen,
  581. cbToRead, &cbRead, NULL))
  582. return ::ReportError(::GetLastError());
  583. cbSeen += cbRead;
  584. } while (cbSeen < cbFile);
  585. m_ptszData[ctc] = _T('\0'); // Nul-terminate the string
  586. LPTSTR ptsz = m_ptszData;
  587. #ifdef _UNICODE
  588. #error "This file should NOT be compiled with _UNICODE defined!!!
  589. // Check for byte-order mark
  590. if (*ptsz == 0xFFFE)
  591. {
  592. // Byte-reversed Unicode file. Swap the hi- and lo-bytes in each wchar
  593. for ( ; ptsz < m_ptszData + ctc; ++ptsz)
  594. {
  595. BYTE* pb = (BYTE*) ptsz;
  596. const BYTE bHi = pb[1];
  597. pb[1] = pb[0];
  598. pb[0] = bHi;
  599. }
  600. ptsz = m_ptszData;
  601. }
  602. if (*ptsz == 0xFEFF)
  603. ++ptsz; // skip the byte-order mark
  604. #endif
  605. // check for the UTF-8 BOM
  606. if ((ctc > 3)
  607. && (ptsz[0] == (TCHAR)0xef)
  608. && (ptsz[1] == (TCHAR)0xbb)
  609. && (ptsz[2] == (TCHAR)0xbf)) {
  610. // note its presence and advance the file pointer past it.
  611. m_fUTF8 = true;
  612. ptsz += 3;
  613. }
  614. // Finally, parse the file
  615. while (ptsz < m_ptszData + ctc)
  616. {
  617. UINT uWeight = GetWeight(ptsz);
  618. // a value of INVALID_WEIGHT for weight indicates that no weight was found,
  619. // i.e. an invalid data file, or the value was not valid.
  620. if (uWeight == INVALID_WEIGHT) {
  621. return ::ReportError((DWORD)ERROR_INVALID_DATA);
  622. }
  623. weightSum += uWeight;
  624. if (weightSum > MAX_WEIGHT) {
  625. return ::ReportError((DWORD)ERROR_INVALID_DATA);
  626. }
  627. LPTSTR ptszTipText = GetTipText(ptsz);
  628. if (!IsBlankString(ptszTipText) && uWeight > 0)
  629. {
  630. CTip* pTip = NULL;
  631. ATLTRY(pTip = new CTip(ptszTipText, uWeight));
  632. if (pTip == NULL)
  633. return ::ReportError(E_OUTOFMEMORY);
  634. AppendTip(pTip);
  635. }
  636. else if (ptsz < m_ptszData + ctc)
  637. {
  638. // not at a terminating "%%" line at the end of the data file
  639. TRACE2("bad tip: tip = `%s', weight = %u\n", ptszTipText, uWeight);
  640. }
  641. }
  642. g_pMonitor->MonitorFile( m_ptszFilename, m_pNotify );
  643. if (m_uTotalWeight == 0 || m_cTips == 0)
  644. return ::ReportError((DWORD)ERROR_INVALID_DATA);
  645. return S_OK;
  646. }
  647. //
  648. // Is ptszFilename the same file as m_ptszFilename in both its name
  649. // and timestamp?
  650. //
  651. HRESULT
  652. CTipList::SameAsCachedFile(
  653. LPCTSTR ptszFilename,
  654. BOOL& rfIsSame)
  655. {
  656. rfIsSame = FALSE;
  657. // Have we cached a file at all?
  658. if (m_ptszFilename == NULL)
  659. return S_OK;
  660. // Are the names the same?
  661. if (_tcsicmp(ptszFilename, m_ptszFilename) != 0)
  662. return S_OK;
  663. #if 1
  664. // FILETIME ftLastWriteTime;
  665. // CHFile hFile(ptszFilename);
  666. //
  667. // if (!hFile)
  668. // return ::ReportError(::GetLastError());
  669. //
  670. // if (!::GetFileTime(hFile, NULL, NULL, &ftLastWriteTime))
  671. // return ::ReportError(::GetLastError());
  672. //
  673. // rfIsSame = (::CompareFileTime(&ftLastWriteTime, &m_ftLastWriteTime) == 0);
  674. if ( !m_pNotify->IsNotified() )
  675. {
  676. rfIsSame = TRUE;
  677. }
  678. #else
  679. // The following is more efficient, but it won't work on Win95 with
  680. // Personal Web Server because GetFileAttributesEx is new to NT 4.0.
  681. WIN32_FILE_ATTRIBUTE_DATA wfad;
  682. if (!::GetFileAttributesEx(ptszFilename, GetFileExInfoStandard,
  683. (LPVOID) &wfad))
  684. return ::ReportError(::GetLastError());
  685. rfIsSame = (::CompareFileTime(&wfad.ftLastWriteTime,
  686. &m_ftLastWriteTime) == 0);
  687. #endif
  688. return S_OK;
  689. }
  690. //
  691. // Generate a random number in the range 0..m_uTotalWeight-1
  692. //
  693. UINT
  694. CTipList::Rand() const
  695. {
  696. UINT u;
  697. ASSERT(m_uTotalWeight > 0);
  698. if (m_uTotalWeight == 1)
  699. return 0;
  700. else if (m_uTotalWeight <= RAND_MAX + 1)
  701. u = rand() % m_uTotalWeight;
  702. else
  703. {
  704. // RAND_MAX is only 32,767. This gives us a bigger range
  705. // of random numbers if the weights are large.
  706. u = ((rand() << 15) | rand()) % m_uTotalWeight;
  707. }
  708. ASSERT(0 <= u && u < m_uTotalWeight);
  709. return u;
  710. }
  711. //
  712. // Append a tip to the list
  713. //
  714. void
  715. CTipList::AppendTip(
  716. CTip* pTip)
  717. {
  718. ASSERT_VALID(this);
  719. pTip->m_pPrev = pTip->m_pNext = NULL;
  720. ASSERT_VALID(pTip);
  721. pTip->m_pPrev = m_pTipsListTail;
  722. if (m_pTipsListTail == NULL)
  723. m_pTipsListHead = pTip;
  724. else
  725. m_pTipsListTail->m_pNext = pTip;
  726. m_pTipsListTail = pTip;
  727. ++m_cTips;
  728. m_uTotalWeight += pTip->m_uWeight;
  729. ASSERT_VALID(this);
  730. }
  731. //
  732. // Remove a tip from somewhere in the list
  733. //
  734. void
  735. CTipList::RemoveTip(
  736. CTip* pTip)
  737. {
  738. ASSERT_VALID(this);
  739. ASSERT_VALID(pTip);
  740. ASSERT(m_cTips > 0);
  741. if (m_cTips == 1)
  742. {
  743. ASSERT(m_pTipsListHead == pTip && pTip == m_pTipsListTail);
  744. m_pTipsListHead = m_pTipsListTail = NULL;
  745. }
  746. else if (pTip == m_pTipsListHead)
  747. {
  748. ASSERT(m_pTipsListHead->m_pNext != NULL);
  749. m_pTipsListHead = m_pTipsListHead->m_pNext;
  750. m_pTipsListHead->m_pPrev = NULL;
  751. }
  752. else if (pTip == m_pTipsListTail)
  753. {
  754. ASSERT(m_pTipsListTail->m_pPrev != NULL);
  755. m_pTipsListTail = m_pTipsListTail->m_pPrev;
  756. m_pTipsListTail->m_pNext = NULL;
  757. }
  758. else
  759. {
  760. ASSERT(m_cTips >= 3);
  761. pTip->m_pPrev->m_pNext = pTip->m_pNext;
  762. pTip->m_pNext->m_pPrev = pTip->m_pPrev;
  763. }
  764. pTip->m_pPrev = pTip->m_pNext = NULL;
  765. --m_cTips;
  766. m_uTotalWeight -= pTip->m_uWeight;
  767. ASSERT_VALID(this);
  768. }
  769. //
  770. // Destroy the list of tips and reset all member variables
  771. //
  772. HRESULT
  773. CTipList::DeleteTips()
  774. {
  775. ASSERT_VALID(this);
  776. CTip* pTip = m_pTipsListHead;
  777. for (UINT i = 0; i < m_cTips; ++i)
  778. {
  779. pTip = pTip->m_pNext;
  780. delete m_pTipsListHead;
  781. m_pTipsListHead = pTip;
  782. }
  783. ASSERT(pTip == NULL && m_pTipsListHead == NULL);
  784. // check for both a valid Filename ptr as well as a valid Monitor ptr.
  785. // If the ContRotModule::Unlock is called prior to this destructor,then
  786. // the Monitor object has already been cleaned up and deleted.
  787. if ( (m_ptszFilename != NULL) && (g_pMonitor != NULL) )
  788. {
  789. g_pMonitor->StopMonitoringFile( m_ptszFilename );
  790. }
  791. delete [] m_ptszFilename;
  792. delete [] m_ptszData;
  793. m_ptszFilename = m_ptszData = NULL;
  794. // m_ftLastWriteTime.dwLowDateTime = m_ftLastWriteTime.dwHighDateTime = 0;
  795. m_cTips = m_uTotalWeight = 0;
  796. m_pTipsListHead = m_pTipsListTail = NULL;
  797. ASSERT_VALID(this);
  798. return S_OK;
  799. }
  800. #if DBG
  801. // Paranoia: check that Tips and TipLists are internally consistent.
  802. // Very useful in catching bugs.
  803. void
  804. CTip::AssertValid() const
  805. {
  806. ASSERT(m_ptsz != NULL && m_uWeight > 0);
  807. ASSERT(0 < m_cServingsLeft && m_cServingsLeft <= m_uWeight);
  808. ASSERT(m_pPrev == NULL || m_pPrev->m_pNext == this);
  809. ASSERT(m_pNext == NULL || m_pNext->m_pPrev == this);
  810. }
  811. void
  812. CTipList::AssertValid() const
  813. {
  814. if (m_cTips == 0)
  815. {
  816. ASSERT(m_pTipsListHead == NULL && m_pTipsListTail == NULL);
  817. ASSERT(m_uTotalWeight == 0);
  818. }
  819. else
  820. {
  821. ASSERT(m_pTipsListHead != NULL && m_pTipsListTail != NULL);
  822. ASSERT(m_pTipsListHead->m_pPrev == NULL);
  823. ASSERT(m_pTipsListTail->m_pNext == NULL);
  824. ASSERT(m_uTotalWeight > 0);
  825. if (m_cTips == 1)
  826. ASSERT(m_pTipsListHead == m_pTipsListTail);
  827. else
  828. ASSERT(m_pTipsListHead != m_pTipsListTail);
  829. }
  830. UINT uWeight = 0;
  831. CTip* pTip = m_pTipsListHead;
  832. UINT i;
  833. for (i = 0; i < m_cTips; ++i)
  834. {
  835. ASSERT_VALID(pTip);
  836. uWeight += pTip->m_uWeight;
  837. if (i < m_cTips - 1)
  838. pTip = pTip->m_pNext;
  839. }
  840. ASSERT(uWeight == m_uTotalWeight);
  841. ASSERT(pTip == m_pTipsListTail);
  842. }
  843. #endif
  844. /////////////////////////////////////////////////////////////////////////////
  845. // Utility functions
  846. //
  847. // Make a copy of a TSTR that can be deleted with operator delete[]
  848. //
  849. static LPTSTR
  850. TcsDup(
  851. LPCTSTR ptsz)
  852. {
  853. LPTSTR ptszNew = NULL;
  854. ATLTRY(ptszNew = new TCHAR [_tcslen(ptsz) + 1]);
  855. if (ptszNew != NULL)
  856. _tcscpy(ptszNew, ptsz);
  857. return ptszNew;
  858. }
  859. //
  860. // reads a \n-terminated string from rptsz and modifies rptsz to
  861. // point after the end of that string
  862. //
  863. static LPTSTR
  864. GetLine(
  865. LPTSTR& rptsz)
  866. {
  867. LPTSTR ptszOrig = rptsz;
  868. LPTSTR ptszEol = _tcspbrk(rptsz, _T("\n"));
  869. if (ptszEol != NULL)
  870. {
  871. // is it "\r\n"?
  872. if (ptszEol > ptszOrig && ptszEol[-1] == _T('\r'))
  873. ptszEol[-1] = _T('\0');
  874. else
  875. ptszEol[0] = _T('\0');
  876. rptsz = ptszEol + 1;
  877. }
  878. else
  879. {
  880. // no newline, so point past the end of the string
  881. rptsz += _tcslen(rptsz);
  882. }
  883. // TRACE1("GetLine: `%s'\n", ptszOrig);
  884. return ptszOrig;
  885. }
  886. //
  887. // Is the string blank?
  888. //
  889. static BOOL
  890. IsBlankString(
  891. LPCTSTR ptsz)
  892. {
  893. if (ptsz == NULL)
  894. return TRUE;
  895. while (*ptsz != _T('\0'))
  896. if (!_istspace(*ptsz))
  897. return FALSE;
  898. else
  899. ptsz++;
  900. return TRUE;
  901. }
  902. //
  903. // Read a weight line from rptsz and update rptsz to point after the
  904. // end of any %% lines.
  905. //
  906. static UINT
  907. GetWeight(
  908. LPTSTR& rptsz)
  909. {
  910. UINT u = INVALID_WEIGHT; // default to invalid weight
  911. while (*rptsz == _T('%'))
  912. {
  913. LPTSTR ptsz = GetLine(rptsz);
  914. if (ptsz[1] == _T('%'))
  915. {
  916. u = 1; // now that the format is correct, default to 1
  917. ptsz +=2; // Skip "%%"
  918. while (*ptsz != _T('\0'))
  919. {
  920. while (_istspace(*ptsz))
  921. ptsz++;
  922. if (*ptsz == _T('/') && ptsz[1] == _T('/'))
  923. {
  924. // TRACE1("// `%s'\n", ptsz+2);
  925. break; // a comment: ignore the rest of the line
  926. }
  927. else if (*ptsz == _T('#'))
  928. {
  929. ptsz++;
  930. if (_T('0') <= *ptsz && *ptsz <= _T('9'))
  931. {
  932. LPTSTR ptsz2;
  933. u = _tcstoul(ptsz, &ptsz2, 10);
  934. ptsz = ptsz2;
  935. // TRACE1("#%u\n", u);
  936. if (u > MAX_WEIGHT)
  937. u = MAX_WEIGHT; // clamp
  938. }
  939. else // ignore word
  940. {
  941. while (*ptsz != _T('\0') && !_istspace(*ptsz))
  942. ptsz++;
  943. }
  944. }
  945. else // ignore word
  946. {
  947. while (*ptsz != _T('\0') && !_istspace(*ptsz))
  948. ptsz++;
  949. }
  950. }
  951. }
  952. }
  953. return u;
  954. }
  955. //
  956. // Read the multiline tip text. Updates rptsz to point past the end of it.
  957. //
  958. static LPTSTR
  959. GetTipText(
  960. LPTSTR& rptsz)
  961. {
  962. LPTSTR ptszOrig = rptsz;
  963. LPTSTR ptszEol = _tcsstr(rptsz, _T("\n%%"));
  964. if (ptszEol != NULL)
  965. {
  966. // is it "\r\n"?
  967. if (ptszEol > rptsz && ptszEol[-1] == _T('\r'))
  968. ptszEol[-1] = _T('\0');
  969. else
  970. ptszEol[0] = _T('\0');
  971. rptsz = ptszEol + 1;
  972. }
  973. else
  974. {
  975. // no "\n%%", so point past the end of the string
  976. rptsz += _tcslen(rptsz);
  977. }
  978. // TRACE1("GetTipText: `%s'\n", ptszOrig);
  979. return ptszOrig;
  980. }
  981. //
  982. // Set the Error Info. It's up to the calling application to
  983. // decide what to do with it. By default, Denali/VBScript will
  984. // print the error number (and message, if there is one) and
  985. // abort the page.
  986. //
  987. static HRESULT
  988. ReportError(
  989. HRESULT hr,
  990. DWORD dwErr)
  991. {
  992. HLOCAL pMsgBuf = NULL;
  993. // If there's a message associated with this error, report that
  994. if (::FormatMessage(
  995. FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
  996. NULL, dwErr,
  997. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
  998. (LPTSTR) &pMsgBuf, 0, NULL)
  999. > 0)
  1000. {
  1001. AtlReportError(CLSID_ContentRotator, (LPCTSTR) pMsgBuf,
  1002. IID_IContentRotator, hr);
  1003. }
  1004. // TODO: add some error messages to the string resources and
  1005. // return those, if FormatMessage doesn't return anything (not
  1006. // all system errors have associated error messages).
  1007. // Free the buffer, which was allocated by FormatMessage
  1008. if (pMsgBuf != NULL)
  1009. ::LocalFree(pMsgBuf);
  1010. return hr;
  1011. }
  1012. //
  1013. // Report a Win32 error code
  1014. //
  1015. static HRESULT
  1016. ReportError(
  1017. DWORD dwErr)
  1018. {
  1019. return ::ReportError(HRESULT_FROM_WIN32(dwErr), dwErr);
  1020. }
  1021. //
  1022. // Report an HRESULT error
  1023. //
  1024. static HRESULT
  1025. ReportError(
  1026. HRESULT hr)
  1027. {
  1028. return ::ReportError(hr, (DWORD) hr);
  1029. }