Source code of Windows XP (NT5)
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.

1859 lines
41 KiB

  1. /*++
  2. Copyright (C) Microsoft Corporation, 1995 - 1999
  3. All rights reserved.
  4. Module Name:
  5. datan.cxx
  6. Abstract:
  7. vDataNotify class, handles communication with uplevel (3.51+)
  8. clients. Data is returned with each notification, so it
  9. is unnecessary to call refresh.
  10. Author:
  11. Albert Ting (AlbertT) 07-11-95
  12. Revision History:
  13. --*/
  14. #include "precomp.hxx"
  15. #pragma hdrstop
  16. #if DBG
  17. //#define DBG_DATANINFO DBG_INFO
  18. #define DBG_DATANINFO DBG_NONE
  19. #endif
  20. #define DEFINE( field, attrib, table, y, z )\
  21. table,
  22. TABLE aTableJob[] = {
  23. #include "ntfyjob.h"
  24. 0 };
  25. TABLE aTablePrinter[] = {
  26. #include "ntfyprn.h"
  27. 0 };
  28. #undef DEFINE
  29. TABLE* aaTable[2] = {
  30. aTablePrinter,
  31. aTableJob
  32. };
  33. /********************************************************************
  34. Hard code the notification structure for uplevel clients.
  35. Later this can be changed for dynamic creation in case the
  36. user wants to change the columns.
  37. ********************************************************************/
  38. /********************************************************************
  39. Jobs.
  40. ********************************************************************/
  41. //
  42. // Index -> Field translation. Used by TData*, and also
  43. // in the PRINTER_NOTIFY_OPTIONS_TYPE structure for FFPCN.
  44. //
  45. FIELD_TABLE
  46. TDataNJob::gFieldTable = {
  47. TDataNJob::kFieldTableSize,
  48. TDataNJob::gaFields
  49. };
  50. FIELD
  51. TDataNJob::gaFields[TDataNJob::kFieldTableSize+1] = {
  52. JOB_COLUMN_FIELDS,
  53. JOB_INDEX_EXTRA_FIELDS,
  54. kInvalidFieldValue
  55. };
  56. FIELD
  57. TDataNJob::gaFieldOther[TDataNJob::kFieldOtherSize] = {
  58. PRINTER_NOTIFY_FIELD_STATUS,
  59. PRINTER_NOTIFY_FIELD_SERVER_NAME,
  60. PRINTER_NOTIFY_FIELD_PRINTER_NAME,
  61. PRINTER_NOTIFY_FIELD_ATTRIBUTES
  62. };
  63. PRINTER_NOTIFY_OPTIONS_TYPE
  64. TDataNJob::gaNotifyOptionsType[TDataNJob::kTypeSize] = {
  65. {
  66. JOB_NOTIFY_TYPE,
  67. 0,
  68. 0,
  69. 0,
  70. TDataNJob::kFieldTableSize,
  71. TDataNJob::gaFields
  72. },
  73. {
  74. PRINTER_NOTIFY_TYPE,
  75. 0,
  76. 0,
  77. 0,
  78. COUNTOF( TDataNJob::gaFieldOther ),
  79. TDataNJob::gaFieldOther
  80. }
  81. };
  82. PRINTER_NOTIFY_OPTIONS
  83. TDataNJob::gNotifyOptions = {
  84. 2,
  85. 0,
  86. COUNTOF( TDataNJob::gaNotifyOptionsType ),
  87. TDataNJob::gaNotifyOptionsType
  88. };
  89. /********************************************************************
  90. Printers.
  91. ********************************************************************/
  92. //
  93. // Index -> Field translation. Used by TData*, and also
  94. // in the PRINTER_NOTIFY_OPTIONS_TYPE structure for FFPCN.
  95. //
  96. FIELD_TABLE
  97. TDataNPrinter::gFieldTable = {
  98. TDataNPrinter::kFieldTableSize,
  99. TDataNPrinter::gaFields
  100. };
  101. FIELD
  102. TDataNPrinter::gaFields[TDataNPrinter::kFieldTableSize+1] = {
  103. PRINTER_COLUMN_FIELDS,
  104. PRINTER_INDEX_EXTRA_FIELDS,
  105. kInvalidFieldValue
  106. };
  107. PRINTER_NOTIFY_OPTIONS_TYPE
  108. TDataNPrinter::gaNotifyOptionsType[TDataNPrinter::kTypeSize] = {
  109. {
  110. PRINTER_NOTIFY_TYPE,
  111. 0,
  112. 0,
  113. 0,
  114. TDataNPrinter::kFieldTableSize,
  115. TDataNPrinter::gaFields
  116. }
  117. };
  118. PRINTER_NOTIFY_OPTIONS
  119. TDataNPrinter::gNotifyOptions = {
  120. 2,
  121. 0,
  122. COUNTOF( TDataNPrinter::gaNotifyOptionsType ),
  123. TDataNPrinter::gaNotifyOptionsType
  124. };
  125. /********************************************************************
  126. VDataNotify
  127. Used in uplevel (NT 3.51+) connections
  128. ********************************************************************/
  129. VDataNotify::
  130. VDataNotify(
  131. IN MDataClient* pDataClient,
  132. IN PFIELD_TABLE pFieldTable,
  133. IN DWORD TypeItem
  134. ) : VData( pDataClient, pFieldTable ), _TypeItem( TypeItem )
  135. /*++
  136. Routine Description:
  137. Create the DataNotify Object.
  138. Arguments:
  139. Return Value:
  140. --*/
  141. {
  142. }
  143. VDataNotify::
  144. ~VDataNotify(
  145. VOID
  146. )
  147. /*++
  148. Routine Description:
  149. Delete the DataNotifyObject.
  150. Arguments:
  151. Return Value:
  152. --*/
  153. {
  154. vDeleteAllItemData();
  155. SPLASSERT( !m_shNotify );
  156. }
  157. /********************************************************************
  158. ********************************************************************/
  159. VOID
  160. VDataNotify::
  161. vProcessNotifyWork(
  162. IN TNotify* pNotify
  163. )
  164. /*++
  165. Routine Description:
  166. Our event was signaled, so we need to pickup any notifications.
  167. This must execute quickly, since it runs in the notification
  168. thread.
  169. Arguments:
  170. pNotify - Notification manager object.
  171. Return Value:
  172. --*/
  173. {
  174. UNREFERENCED_PARAMETER( pNotify );
  175. DWORD dwChange;
  176. PPRINTER_NOTIFY_INFO pInfo = NULL;
  177. //
  178. // Notification caught. We must signal that caught it before
  179. // issuing the Notify, since there may be notifications between
  180. // the Notify and the FNPCN, which would be lost.
  181. //
  182. BOOL bSuccess = FindNextPrinterChangeNotification( m_shNotify,
  183. &dwChange,
  184. 0,
  185. (PVOID*)&pInfo );
  186. if( !bSuccess || dwChange & PRINTER_CHANGE_FAILED_CONNECTION_PRINTER ){
  187. DBGMSG( DBG_WARN,
  188. ( "DataNotify.vProcessNotifyWork: %x FNPCN %x dwChange %d failed: %d\n",
  189. this,
  190. static_cast<HANDLE>(m_shNotify),
  191. dwChange,
  192. GetLastError( )));
  193. }
  194. //
  195. // Check for a discard.
  196. //
  197. if( !pInfo || (pInfo->Flags & PRINTER_NOTIFY_INFO_DISCARDED )){
  198. DBGMSG( DBG_NOTIFY,
  199. ( "DataNotify.vProcessNotifyWork: %x discard %x pInfo %x %x\n",
  200. this,
  201. static_cast<HANDLE>(m_shNotify),
  202. pInfo,
  203. pInfo ?
  204. pInfo->Flags :
  205. 0 ));
  206. INFO Info;
  207. Info.dwData = TPrinter::kExecRefresh;
  208. _pDataClient->vContainerChanged( kContainerStateVar, Info );
  209. } else {
  210. #if DBG
  211. vDbgOutputInfo( pInfo );
  212. #endif
  213. //
  214. // Post the work to the message thread. If pInfo is NULL, then
  215. // a reopen will be requested.
  216. //
  217. vBlockAdd( kProcessIncremental, dwChange, (HBLOCK)pInfo );
  218. //
  219. // vBlockAdd has adopted pInfo; set it to NULL
  220. // so we don't free it.
  221. //
  222. pInfo = NULL;
  223. }
  224. //
  225. // Since we adopted pInfo, free it now if we haven't passed it to
  226. // some other function.
  227. //
  228. if( pInfo ){
  229. FreePrinterNotifyInfo( pInfo );
  230. }
  231. }
  232. /********************************************************************
  233. Data interface for Notify.
  234. ********************************************************************/
  235. HITEM
  236. VDataNotify::
  237. GetItem(
  238. IN NATURAL_INDEX NaturalIndex
  239. ) const
  240. /*++
  241. Routine Description:
  242. Retrieve a handle to the item based on the NaturalIndex.
  243. The data is stored as a linked list, so walk the list
  244. and return a pointer to TItemData*.
  245. Arguments:
  246. NaturalIndex - Index of printing order for index to retrieve.
  247. Return Value:
  248. HITEM, of NULL if ItemData not found.
  249. --*/
  250. {
  251. SPLASSERT( NaturalIndex < VData::UIGuard._cItems );
  252. TIter Iter;
  253. for( UIGuard.ItemData_vIterInit( Iter ), Iter.vNext();
  254. Iter.bValid();
  255. Iter.vNext(), --NaturalIndex ){
  256. if( !NaturalIndex ){
  257. return (HITEM)UIGuard.ItemData_pConvert( Iter );
  258. }
  259. }
  260. DBGMSG( DBG_ERROR,
  261. ( "DataNotify.GetItem: NI %d not found %x\n", this ));
  262. return NULL;
  263. }
  264. HITEM
  265. VDataNotify::
  266. GetNextItem(
  267. IN HITEM hItem
  268. ) const
  269. /*++
  270. Routine Description:
  271. Gets the next item based on the one passed in.
  272. There is no runtime checking to ensure that client doesn't walk
  273. off the end of the list.
  274. Arguments:
  275. hItem - Handle to item immediately before the one returned.
  276. If NULL, returns first item.
  277. Return Value:
  278. Next item after hItem.
  279. --*/
  280. {
  281. TItemData* pItemData = (TItemData*)hItem;
  282. //
  283. // NULL passed in, return first item.
  284. //
  285. if( !hItem ){
  286. SPLASSERT( VData::UIGuard._cItems > 0 );
  287. return UIGuard.ItemData_pHead();
  288. }
  289. //
  290. // Ensure we are not walking off the end of the list.
  291. //
  292. SPLASSERT( UIGuard.ItemData_bValid( pItemData->ItemData_pdlNext( )));
  293. return (HITEM)( pItemData->ItemData_pNext( ));
  294. }
  295. INFO
  296. VDataNotify::
  297. GetInfo(
  298. IN HITEM hItem,
  299. IN DATA_INDEX DataIndex
  300. ) const
  301. /*++
  302. Routine Description:
  303. Returns information about a item based on the index.
  304. Arguments:
  305. hItem - Item to get information about.
  306. DataIndex - Index into pFieldTable->pFields, indicating type of
  307. information requested.
  308. Return Value:
  309. INFO - Information about item.
  310. --*/
  311. {
  312. TItemData* pItemData = (TItemData*)hItem;
  313. //
  314. // We don't need to do a translation since they are stored in
  315. // index format anyway.
  316. //
  317. SPLASSERT( DataIndex < _pFieldTable->cFields );
  318. return pItemData->_aInfo[DataIndex];
  319. }
  320. IDENT
  321. VDataNotify::
  322. GetId(
  323. IN HANDLE hItem
  324. ) const
  325. /*++
  326. Routine Description:
  327. Retrieves JobId from hItem.
  328. Arguments:
  329. hItem - Item to retrieve Id from.
  330. Return Value:
  331. ID.
  332. --*/
  333. {
  334. TItemData* pItemData = (TItemData*)hItem;
  335. return pItemData->_Id;
  336. }
  337. NATURAL_INDEX
  338. VDataNotify::
  339. GetNaturalIndex(
  340. IN IDENT Id,
  341. OUT HITEM* phItem OPTIONAL
  342. ) const
  343. /*++
  344. Routine Description:
  345. Retrieves NaturalIndex, and optionally hItem based on Id.
  346. Arguments:
  347. Id - Job Id to search for.
  348. phItem - Optional, receives handle to Job matching Job Id.
  349. Return Value:
  350. NATURAL_INDEX.
  351. --*/
  352. {
  353. //
  354. // Scan through linked list looking for the right ID.
  355. //
  356. TIter Iter;
  357. NATURAL_INDEX NaturalIndex;
  358. TItemData* pItemData;
  359. if( phItem ){
  360. *phItem = NULL;
  361. }
  362. for( NaturalIndex = 0, UIGuard.ItemData_vIterInit( Iter ), Iter.vNext();
  363. Iter.bValid();
  364. ++NaturalIndex, Iter.vNext( )){
  365. pItemData = UIGuard.ItemData_pConvert( Iter );
  366. if( Id == pItemData->Id( )){
  367. if( phItem ){
  368. *phItem = (PHITEM)pItemData;
  369. }
  370. return NaturalIndex;
  371. }
  372. }
  373. DBGMSG( DBG_DATANINFO,
  374. ( "DataNotify.GetNaturalIndex: ItemData id %x not found %x\n", Id, this ));
  375. return kInvalidNaturalIndexValue;
  376. }
  377. /********************************************************************
  378. VDataNotify::TItemData
  379. ********************************************************************/
  380. VDataNotify::TItemData*
  381. VDataNotify::TItemData::
  382. pNew(
  383. IN VDataNotify* pDataNotify,
  384. IN IDENT Id
  385. )
  386. /*++
  387. Routine Description:
  388. Allocate a new TItemData* structure. Note that this is not a first-
  389. class C++ object; it is a variable size structure. Consequently,
  390. it must be allocated and freed using pNew and vDelete.
  391. Note: if the pData->pPrinter->pFieldsFromIndex changes, the
  392. TItemData* members must be deleted first, since we need to
  393. delete strings.
  394. We could implement deletion of data members using an
  395. abc, but this would require storing a vtbl with each field.
  396. A better way would be to implement singleton classes where the
  397. data is passed in explicitly.
  398. Arguments:
  399. pDataNotify - Owning pDataNotify. We need this to determine
  400. how bit the aInfo array should be.
  401. Id - Job Id.
  402. Return Value:
  403. TItemData* or NULL on failure.
  404. --*/
  405. {
  406. UINT uSize = sizeof( TItemData ) +
  407. ( pDataNotify->_pFieldTable->cFields - 1 ) * sizeof( INFO );
  408. TItemData* pItemData = (TItemData*)AllocMem( uSize );
  409. if( !pItemData ){
  410. return NULL;
  411. }
  412. ZeroMemory( pItemData, uSize );
  413. pItemData->_pDataNotify = pDataNotify;
  414. pItemData->_Id = Id;
  415. return pItemData;
  416. }
  417. VOID
  418. VDataNotify::TItemData::
  419. vDelete(
  420. VOID
  421. )
  422. /*++
  423. Routine Description:
  424. Delete the TItemData. This routine must be called instead of using
  425. the delete operator.
  426. Arguments:
  427. Return Value:
  428. --*/
  429. {
  430. PFIELD pFields = _pDataNotify->_pFieldTable->pFields;
  431. COUNT cItemData = _pDataNotify->_pFieldTable->cFields;
  432. PINFO pInfo;
  433. UINT i;
  434. for( i= 0, pInfo = _aInfo;
  435. i < cItemData;
  436. ++i, ++pInfo, ++pFields ){
  437. if( aaTable[_pDataNotify->_TypeItem][*pFields] != TABLE_DWORD ){
  438. FreeMem( pInfo->pvData );
  439. }
  440. }
  441. FreeMem( this );
  442. }
  443. /********************************************************************
  444. Worker thread functions for Notify.
  445. ********************************************************************/
  446. STATEVAR
  447. VDataNotify::
  448. svNotifyStart(
  449. IN STATEVAR StateVar
  450. )
  451. /*++
  452. Routine Description:
  453. Begin uplevel notifcations.
  454. Arguments:
  455. Return Value:
  456. --*/
  457. {
  458. TStatus Status;
  459. //
  460. // Get the notification handle.
  461. //
  462. m_shNotify = FindFirstPrinterChangeNotification(
  463. _pDataClient->hPrinter(),
  464. 0,
  465. 0,
  466. pNotifyOptions( ));
  467. if( !m_shNotify )
  468. {
  469. DBGMSG( DBG_WARN,
  470. ( "DataNotify.svNotifyStart: FFPCN failed %d\n",
  471. GetLastError( )));
  472. goto Fail;
  473. }
  474. DBGMSG( DBG_NOTIFY,
  475. ( "DataNotify.svNotifyStart: %x FFPCN success returns 0x%x\n",
  476. _pDataClient->hPrinter(),
  477. static_cast<HANDLE>(m_shNotify) ));
  478. //
  479. // Successfully opened, request that it be registered and then
  480. // refresh.
  481. //
  482. return (StateVar & ~TPrinter::kExecNotifyStart) |
  483. TPrinter::kExecRegister | TPrinter::kExecRefresh;
  484. Fail:
  485. //
  486. // Force a reopen. Everything gets reset (handles closed, etc.) when
  487. // the reopen occurs.
  488. //
  489. return StateVar | TPrinter::kExecDelay | TPrinter::kExecReopen;
  490. }
  491. STATEVAR
  492. VDataNotify::
  493. svNotifyEnd(
  494. IN STATEVAR StateVar
  495. )
  496. /*++
  497. Routine Description:
  498. Stop downlevel notifications.
  499. Arguments:
  500. Return Value:
  501. --*/
  502. {
  503. DBGMSG( DBG_NOTIFY, ( "DataNotify.svNotifyEnd: handle %x\n", static_cast<HANDLE>(m_shNotify) ));
  504. //
  505. // Unregister from TNotify.
  506. //
  507. _pPrintLib->pNotify()->sUnregister( this );
  508. //
  509. // If we have a notification event, close it.
  510. //
  511. m_shNotify = NULL;
  512. return StateVar & ~TPrinter::kExecNotifyEnd;
  513. }
  514. STATEVAR
  515. VDataNotify::
  516. svRefresh(
  517. IN STATEVAR StateVar
  518. )
  519. /*++
  520. Routine Description:
  521. Refresh the printer data object.
  522. Arguments:
  523. Return Value:
  524. --*/
  525. {
  526. PRINTER_NOTIFY_OPTIONS Options;
  527. PPRINTER_NOTIFY_INFO pInfo;
  528. BOOL bReturn;
  529. DWORD dwChange;
  530. //
  531. // We must unregister ourselves to guarantee that there
  532. // are no synchronization problems:
  533. //
  534. // There are two threads: this one is requesting a refresh,
  535. // while the other one is waiting for notifications about a
  536. // printer. When the notify thread gets a change, it puts
  537. // it on a linked list.
  538. //
  539. // If you are still registered, then there is a window where
  540. // you can get a notification, and put it on your linked list
  541. // before you call FNPCN to do the refresh. When you call
  542. // FNPCN, the spooler will clear out any stale data in its
  543. // datastructure, but not our internal linked list.
  544. //
  545. // The fix is to remove ourselves from the notification list
  546. // (via sUnregister). handle the refresh, then add ourselves back
  547. // to the list (sRegister), thus preventing any stale data
  548. // from coming from the notification thread.
  549. //
  550. _pPrintLib->pNotify()->sUnregister( this );
  551. Options.Version = 2;
  552. Options.Flags = PRINTER_NOTIFY_OPTIONS_REFRESH;
  553. Options.Count = 0;
  554. Options.pTypes = NULL;
  555. bReturn = FindNextPrinterChangeNotification(
  556. m_shNotify,
  557. &dwChange,
  558. &Options,
  559. (PVOID*)&pInfo);
  560. if( !bReturn || !pInfo ){
  561. DBGMSG( DBG_WARN,
  562. ( "DataNotify.svRefresh Failed %d\n", GetLastError( )));
  563. return StateVar | TPrinter::kExecReopen | TPrinter::kExecDelay;
  564. }
  565. vBlockAdd( kProcessRefresh, 0, (HBLOCK)pInfo );
  566. TStatus Status;
  567. Status DBGCHK = _pPrintLib->pNotify()->sRegister( this );
  568. if( Status != ERROR_SUCCESS ){
  569. //
  570. // Failed to register; delay then reopen printer. We could
  571. // just try and re-register later, but this should be a very
  572. // rare event, so do the least amount of work.
  573. //
  574. DBGMSG( DBG_WARN,
  575. ( "DataNotify.svRefresh: sRegister %x failed %d\n",
  576. this, Status ));
  577. StateVar |= TPrinter::kExecDelay | TPrinter::kExecReopen;
  578. }
  579. return StateVar & ~TPrinter::kExecRefreshAll;
  580. }
  581. /********************************************************************
  582. UI Thread interaction routines.
  583. ********************************************************************/
  584. VOID
  585. VDataNotify::
  586. vBlockProcessImp(
  587. IN DWORD dwParam1,
  588. IN DWORD dwParam2,
  589. IN HBLOCK hBlock ADOPT
  590. )
  591. /*++
  592. Routine Description:
  593. Take a job block and update the internal data structure. This
  594. function will call back into _pDataClient to Notify the screen.
  595. Arguments:
  596. dwParam1 - job count
  597. dwParam2 - Change flags.
  598. hBlock - PJOB_INFO_2 block
  599. Return Value:
  600. --*/
  601. {
  602. UNREFERENCED_PARAMETER( dwParam2 );
  603. SPLASSERT( hBlock );
  604. BOOL bRestoreSelections = FALSE;
  605. //
  606. // If this is a complete refresh, then clear everything.
  607. //
  608. if( dwParam1 == kProcessRefresh ){
  609. //
  610. // Save the selections since we are refreshing jobs.
  611. //
  612. _pDataClient->vSaveSelections();
  613. bRestoreSelections = TRUE;
  614. //
  615. // Clear out all jobs. Below in pInfo processing,
  616. // we will add them back in.
  617. //
  618. _pDataClient->vContainerChanged( kContainerReloadItems, kInfoNull );
  619. //
  620. // Delete our data structure.
  621. //
  622. vDeleteAllItemData();
  623. DBGMSG( DBG_DATANINFO,
  624. ( "DataNotify.vBlockProcessImp: DataN %x P:%x %x Refresh\n",
  625. this, _pDataClient, hBlock ));
  626. }
  627. //
  628. // No need to grab any critical sections since we are in
  629. // the UI thread.
  630. //
  631. PPRINTER_NOTIFY_INFO_DATA pData;
  632. PPRINTER_NOTIFY_INFO pInfo = (PPRINTER_NOTIFY_INFO)hBlock;
  633. COUNT i;
  634. CACHE Cache;
  635. Cache.pItemData = NULL;
  636. Cache.Id = kInvalidIdentValue;
  637. Cache.NaturalIndex = kInvalidNaturalIndexValue;
  638. Cache.bNew = FALSE;
  639. #if DBG
  640. vDbgOutputInfo( pInfo );
  641. #endif
  642. for( pData = pInfo->aData, i = pInfo->Count; i; --i, ++pData ){
  643. //
  644. // Switch based on type.
  645. //
  646. if( pData->Type != _TypeItem ){
  647. //
  648. // Looks for changes in status, attributes, or name.
  649. //
  650. vContainerProcess( pData );
  651. } else {
  652. //
  653. // Process changes in jobs. We cache the latest Id->
  654. // NaturalIndex mapping since they tend to come in clumps.
  655. //
  656. if( !bItemProcess( pData, Cache )){
  657. INFO Info;
  658. Info.dwData = TPrinter::kExecRefresh | TPrinter::kExecDelay;
  659. _pDataClient->vContainerChanged( kContainerStateVar, Info );
  660. }
  661. }
  662. }
  663. //
  664. // Notify the client that the refresh block has been processed.
  665. // When processing a refresh, the folder does not want to send
  666. // notification for each item. Instead, it will refresh everything
  667. // once the refresh is complete.
  668. //
  669. if( dwParam1 == kProcessRefresh ){
  670. _pDataClient->vContainerChanged( kContainerRefreshComplete, kInfoNull );
  671. }
  672. FreePrinterNotifyInfo( (PPRINTER_NOTIFY_INFO)hBlock );
  673. if( bRestoreSelections ){
  674. _pDataClient->vRestoreSelections();
  675. }
  676. }
  677. VOID
  678. VDataNotify::
  679. vBlockDelete(
  680. IN OUT HBLOCK hBlock ADOPT
  681. )
  682. /*++
  683. Routine Description:
  684. Free a Block. Called when the PostMessage fails and the
  685. job block needs to be destroyed.
  686. Arguments:
  687. hBlock - Job block to delete.
  688. Return Value:
  689. --*/
  690. {
  691. FreePrinterNotifyInfo( (PPRINTER_NOTIFY_INFO)hBlock );
  692. }
  693. VOID
  694. VDataNotify::
  695. vContainerProcess(
  696. IN const PPRINTER_NOTIFY_INFO_DATA pData
  697. )
  698. /*++
  699. Routine Description:
  700. Process printer specific change information.
  701. Arguments:
  702. pData - Data about the printer change.
  703. Return Value:
  704. --*/
  705. {
  706. INFO Info;
  707. CONTAINER_CHANGE ContainerChange = kContainerNull;
  708. //
  709. // Processes changes in printer.
  710. //
  711. switch( pData->Field ){
  712. case PRINTER_NOTIFY_FIELD_STATUS:
  713. ContainerChange = kContainerStatus;
  714. Info.dwData = pData->NotifyData.adwData[0];
  715. break;
  716. case PRINTER_NOTIFY_FIELD_ATTRIBUTES:
  717. ContainerChange = kContainerAttributes;
  718. Info.dwData = pData->NotifyData.adwData[0];
  719. break;
  720. case PRINTER_NOTIFY_FIELD_SERVER_NAME:
  721. ContainerChange = kContainerServerName;
  722. Info.pszData = (LPTSTR)pData->NotifyData.Data.pBuf;
  723. break;
  724. case PRINTER_NOTIFY_FIELD_PRINTER_NAME:
  725. ContainerChange = kContainerName;
  726. Info.pszData = (LPTSTR)pData->NotifyData.Data.pBuf;
  727. break;
  728. default:
  729. DBGMSG( DBG_ERROR,
  730. ( "DataNotify.vPrinterProcess: Unknown field %d\n",
  731. pData->Field ));
  732. return;
  733. }
  734. _pDataClient->vContainerChanged( ContainerChange, Info );
  735. }
  736. BOOL
  737. VDataNotify::
  738. bItemProcess(
  739. IN const PPRINTER_NOTIFY_INFO_DATA pData,
  740. IN CACHE& Cache CHANGE
  741. )
  742. {
  743. //
  744. // Process a single Item change.
  745. //
  746. TItemData* pItemData;
  747. NATURAL_INDEX NaturalIndex = kInvalidNaturalIndexValue;
  748. FIELD Field = pData->Field;
  749. //
  750. // Try and match Id with TItemData.
  751. //
  752. if( pData->Id != Cache.Id ){
  753. //
  754. // ItemData has not been cached.
  755. //
  756. NaturalIndex = GetNaturalIndex( pData->Id, (PHITEM)&pItemData );
  757. //
  758. // Now cache it.
  759. //
  760. Cache.Id = pData->Id;
  761. Cache.NaturalIndex = NaturalIndex;
  762. Cache.pItemData = pItemData;
  763. } else {
  764. //
  765. // Retrieve data from cache.
  766. //
  767. NaturalIndex = Cache.NaturalIndex;
  768. pItemData = Cache.pItemData;
  769. SPLASSERT( pItemData );
  770. }
  771. //
  772. // Check if the ItemData is new and should be created.
  773. //
  774. if( !pItemData ){
  775. //
  776. // Create Item now, and append to end of linked list.
  777. //
  778. pItemData = pNewItemData( this, pData->Id );
  779. if( !pItemData ){
  780. Cache.Id = kInvalidIdentValue;
  781. return FALSE;
  782. }
  783. //
  784. // Update the cache. The Item is always initally added
  785. // to the end of the list view, so NaturalIndex = current
  786. // Item count.
  787. //
  788. NaturalIndex =
  789. Cache.NaturalIndex = VData::UIGuard._cItems;
  790. Cache.pItemData = pItemData;
  791. Cache.bNew = TRUE;
  792. ++VData::UIGuard._cItems;
  793. UIGuard.ItemData_vAppend( pItemData );
  794. SPLASSERT( pItemData == (TItemData*)GetItem( NaturalIndex ));
  795. }
  796. //
  797. // Match the pData->Field with our field column.
  798. //
  799. PFIELD pField;
  800. DATA_INDEX DataIndex;
  801. for( pField = _pFieldTable->pFields, DataIndex = 0;
  802. *pField != kInvalidFieldValue;
  803. ++pField, ++DataIndex ){
  804. if( Field == *pField ){
  805. //
  806. // Found field -- update it. If the return value is TRUE,
  807. // then the item must be deleted.
  808. //
  809. if( bUpdateInfo( pData, DataIndex, Cache )){
  810. DBGMSG( DBG_TRACE,
  811. ( "DataNotify.bItemProcess: delete %x Id %x NI %d cItems %d\n",
  812. pItemData, Cache.Id, Cache.NaturalIndex,
  813. VData::UIGuard._cItems - 1 ));
  814. pItemData->ItemData_vDelinkSelf();
  815. pItemData->vDelete();
  816. SPLASSERT( VData::UIGuard._cItems );
  817. --VData::UIGuard._cItems;
  818. //
  819. // The cache is now invalid.
  820. //
  821. Cache.Id = kInvalidIdentValue;
  822. }
  823. break;
  824. }
  825. }
  826. SPLASSERT( *pField != kInvalidFieldValue );
  827. return TRUE;
  828. }
  829. VOID
  830. VDataNotify::
  831. vUpdateInfoData(
  832. IN const PPRINTER_NOTIFY_INFO_DATA pData,
  833. IN TABLE Table,
  834. IN PINFO pInfo
  835. )
  836. /*++
  837. Routine Description:
  838. Update the VDataNotify internal data structure, and issue
  839. a vItemChanged to the queue UI to keep it in sync.
  840. Arguments:
  841. pData - Data item that changed.
  842. Table - Type of data stored in pData.
  843. pInfo - Pointer to INFO receiving new data. Old data freed if
  844. necessary.
  845. Return Value:
  846. --*/
  847. {
  848. switch( Table ){
  849. case TABLE_DWORD:
  850. pInfo->dwData = pData->NotifyData.adwData[0];
  851. break;
  852. default:
  853. SPLASSERT( pData->NotifyData.Data.cbBuf );
  854. FreeMem( pInfo->pvData );
  855. pInfo->pvData = AllocMem( pData->NotifyData.Data.cbBuf );
  856. if( pInfo->pvData ){
  857. CopyMemory( pInfo->pvData,
  858. pData->NotifyData.Data.pBuf,
  859. pData->NotifyData.Data.cbBuf );
  860. }
  861. break;
  862. }
  863. }
  864. VOID
  865. VDataNotify::
  866. vDeleteAllItemData(
  867. VOID
  868. )
  869. /*++
  870. Routine Description:
  871. Delete all TItemData* jobs. Normally there should be an assert
  872. to check whether this is called from the UI thread, but
  873. the final delete case calls this from a worker thread.
  874. Arguments:
  875. Return Value:
  876. --*/
  877. {
  878. //
  879. // Optimization: instead of delinking each individually, just
  880. // free them then reset the whole list.
  881. //
  882. TIter Iter;
  883. TItemData* pItemData;
  884. for( UIGuard.ItemData_vIterInit( Iter ), Iter.vNext();
  885. Iter.bValid(); ){
  886. //
  887. // Get a pointer to the next object, then increment the iter.
  888. // We must increment before deleting, since once the object
  889. // has been deleted, the next pointer is trash.
  890. //
  891. pItemData = UIGuard.ItemData_pConvert( Iter );
  892. Iter.vNext();
  893. pItemData->vDelete();
  894. }
  895. //
  896. // Now that all ItemData have been deleted, reset the head link.
  897. //
  898. UIGuard.ItemData_vReset();
  899. VData::UIGuard._cItems = 0;
  900. }
  901. /********************************************************************
  902. TDataNJob
  903. ********************************************************************/
  904. TDataNJob::
  905. TDataNJob(
  906. IN MDataClient* pDataClient
  907. ) : VDataNotify( pDataClient,
  908. &TDataNJob::gFieldTable,
  909. JOB_NOTIFY_TYPE )
  910. {
  911. }
  912. TDataNJob::
  913. ~TDataNJob(
  914. VOID
  915. )
  916. {
  917. }
  918. BOOL
  919. TDataNJob::
  920. bUpdateInfo(
  921. IN const PPRINTER_NOTIFY_INFO_DATA pData,
  922. IN DATA_INDEX DataIndex,
  923. IN CACHE& Cache CHANGE
  924. )
  925. {
  926. SPLASSERT( DataIndex < _pFieldTable->cFields );
  927. SPLASSERT( Cache.NaturalIndex < VData::UIGuard._cItems );
  928. TItemData* pItemData = Cache.pItemData;
  929. FIELD Field = pData->Field;
  930. INFO Info;
  931. INFO InfoNew;
  932. ITEM_CHANGE ItemChange = kItemInfo;
  933. Info.NaturalIndex = Cache.NaturalIndex;
  934. InfoNew.NaturalIndex = kInvalidNaturalIndexValue;
  935. TABLE Table = pData->Reserved & 0xffff;
  936. PINFO pInfo = &pItemData->_aInfo[DataIndex];
  937. if( Cache.bNew ){
  938. //
  939. // This is a new item; inform the client to create a new item.
  940. //
  941. _pDataClient->vItemChanged( kItemCreate,
  942. (HITEM)pItemData,
  943. Info,
  944. Info );
  945. Cache.bNew = FALSE;
  946. }
  947. switch( Field ){
  948. case JOB_NOTIFY_FIELD_POSITION:
  949. //
  950. // Special case MOVED.
  951. //
  952. // Move the ItemData to the correct place in the linked list.
  953. //
  954. ItemChange = kItemPosition;
  955. //
  956. // Valid range is from 1 -> VData::UIGuard._cItems.
  957. //
  958. SPLASSERT( pData->NotifyData.adwData[0] );
  959. //
  960. // Start with base instead of head, since position 2 =
  961. // second element, not third.
  962. //
  963. InfoNew.NaturalIndex = pData->NotifyData.adwData[0] - 1;
  964. DBGMSG( DBG_DATANINFO,
  965. ( "DataNotify.vUpdateInfo: Position changed id %d ni %d->%d\n",
  966. pData->Id,
  967. Info.NaturalIndex,
  968. InfoNew.NaturalIndex ));
  969. if( InfoNew.NaturalIndex >= VData::UIGuard._cItems ){
  970. DBGMSG( DBG_WARN,
  971. ( "DataNotify.vUpdateInfo: %x %d %d, moving past end Item.\n",
  972. this,
  973. InfoNew.NaturalIndex,
  974. VData::UIGuard._cItems ));
  975. return FALSE;
  976. }
  977. //
  978. // Remove ourselves and traverse until we get to the
  979. // right ItemData. Then add ourselves back in.
  980. //
  981. pItemData->ItemData_vDelinkSelf();
  982. NATURAL_INDEX i;
  983. TIter Iter;
  984. for( i = InfoNew.NaturalIndex, UIGuard.ItemData_vIterInit( Iter ),
  985. Iter.vNext();
  986. i;
  987. --i, Iter.vNext( )){
  988. SPLASSERT( UIGuard.ItemData_bValid( Iter ));
  989. }
  990. //
  991. // Insert before appropriate element.
  992. //
  993. UIGuard.ItemData_vInsertBefore( Iter, pItemData );
  994. if( InfoNew.NaturalIndex != Info.NaturalIndex ){
  995. DBGMSG( DBG_DATANINFO,
  996. ( "DataNotify.vUpdateInfo: ItemData %x (%d) moved from %d to %d\n",
  997. pItemData, pItemData->_Id, Info.NaturalIndex,
  998. InfoNew.NaturalIndex ));
  999. } else {
  1000. //
  1001. // If the Item didn't move, don't do anything. This occurs the
  1002. // first time a Item comes in since the refresh returns the
  1003. // position, even though it really didn't move.
  1004. //
  1005. DBGMSG( DBG_DATANINFO,
  1006. ( "DataNotify.vUpdateInfo: ItemData %x (%d) did not move %d\n",
  1007. pItemData, pItemData->_Id, Info.NaturalIndex ));
  1008. return FALSE;
  1009. }
  1010. //
  1011. // Update cache.
  1012. //
  1013. Cache.NaturalIndex = InfoNew.NaturalIndex;
  1014. break;
  1015. case JOB_NOTIFY_FIELD_STATUS:
  1016. if (pData->NotifyData.adwData[0] & JOB_STATUS_DELETED ){
  1017. //
  1018. // Special case deletion.
  1019. //
  1020. //
  1021. // Ensure Item we are deleting matches NaturalIndex.
  1022. //
  1023. SPLASSERT( GetItem( Info.NaturalIndex ) == (HITEM)pItemData );
  1024. //
  1025. // Now inform the UI that something changed.
  1026. //
  1027. _pDataClient->vItemChanged( kItemDelete,
  1028. (HITEM)pItemData,
  1029. Info,
  1030. InfoNew );
  1031. return TRUE;
  1032. }
  1033. goto DefaultAction;
  1034. case JOB_NOTIFY_FIELD_DOCUMENT:
  1035. //
  1036. // Item disposition is slightly different: it still
  1037. // is an INFO, but we need to change the width of
  1038. // the first field (document name).
  1039. //
  1040. ItemChange = kItemName;
  1041. //
  1042. // The name really didn't change (job ids never change) but
  1043. // the function specifies that the old and new names are
  1044. // placed in Info and InfoNew.
  1045. //
  1046. InfoNew = Info;
  1047. //
  1048. // Fall through to default:
  1049. //
  1050. default:
  1051. DefaultAction:
  1052. //
  1053. // Update the standard data item.
  1054. //
  1055. vUpdateInfoData( pData, Table, pInfo );
  1056. break;
  1057. }
  1058. //
  1059. // Now inform the UI that something changed.
  1060. //
  1061. _pDataClient->vItemChanged( ItemChange,
  1062. (HITEM)pItemData,
  1063. Info,
  1064. InfoNew );
  1065. return FALSE;
  1066. }
  1067. /********************************************************************
  1068. TDataNPrinter
  1069. ********************************************************************/
  1070. TDataNPrinter::
  1071. TDataNPrinter(
  1072. IN MDataClient* pDataClient
  1073. ) : VDataNotify( pDataClient,
  1074. &TDataNPrinter::gFieldTable,
  1075. PRINTER_NOTIFY_TYPE )
  1076. {
  1077. }
  1078. TDataNPrinter::
  1079. ~TDataNPrinter(
  1080. VOID
  1081. )
  1082. {
  1083. }
  1084. VDataNotify::TItemData*
  1085. TDataNPrinter::
  1086. pNewItemData(
  1087. VDataNotify* pDataNotify,
  1088. IDENT Id
  1089. )
  1090. /*++
  1091. Routine Description:
  1092. Override the standard definition of allocating a new TItemData.
  1093. Normally this is a simple zero-initialized structure, but in
  1094. the printer case, we need to initialized the CJobs field to be
  1095. non-zero.
  1096. This fixes the case where a printer is pending deletion and has
  1097. >0 jobs. Otherwise, when the status notification comes in,
  1098. we see that the printer is pending deletion and has zero jobs
  1099. (since the CJobs field hasn't been parsed yet) so we delete the
  1100. printer. Then the other notifications about this printer come in,
  1101. causing us to recreate it. However, now the name is lost (since
  1102. it came in first) and when someone tries to enumerate, the will
  1103. AV because there's no string.
  1104. Arguments:
  1105. pDataNotify - Owning pDataNotify.
  1106. Id - Id of new ItemData.
  1107. Return Value:
  1108. TItemData*; NULL in case of failure.
  1109. --*/
  1110. {
  1111. TItemData* pItemData = TItemData::pNew( pDataNotify, Id );
  1112. if( pItemData ){
  1113. pItemData->_aInfo[kIndexCJobs].dwData = (DWORD)-1;
  1114. }
  1115. return pItemData;
  1116. }
  1117. BOOL
  1118. TDataNPrinter::
  1119. bUpdateInfo(
  1120. IN const PPRINTER_NOTIFY_INFO_DATA pData,
  1121. IN DATA_INDEX DataIndex,
  1122. IN CACHE& Cache CHANGE
  1123. )
  1124. {
  1125. ITEM_CHANGE ItemChange;
  1126. TABLE Table = pData->Reserved & 0xffff;
  1127. TItemData* pItemData = Cache.pItemData;
  1128. PINFO pInfo = &pItemData->_aInfo[DataIndex];
  1129. INFO Info;
  1130. INFO InfoNew;
  1131. switch( pData->Field ){
  1132. case PRINTER_NOTIFY_FIELD_PRINTER_NAME: {
  1133. //
  1134. // The printer name is changing. Pass in the old name to
  1135. // the client, along with the old.
  1136. //
  1137. Info = pItemData->_aInfo[kIndexPrinterName];
  1138. InfoNew.pvData = pData->NotifyData.Data.pBuf;
  1139. //
  1140. // The folder code has an interesting problem: it always
  1141. // identifies items by string names instead of IDs, so when
  1142. // a new item is created, we must have a printer name.
  1143. // We solve this problem by using the sending an kItemCreate
  1144. // only when we have the printer name. Note this relies
  1145. // on the fact that the name is always sent as the first
  1146. // change field, a hack.
  1147. //
  1148. if( Cache.bNew ){
  1149. //
  1150. // This is a creation event since bNew is set. The old
  1151. // name is NULL (since it's new), but the SHChangeNotify
  1152. // requires the name, so set the "old" name to be the
  1153. // new one. Then mark that the item is no longer new.
  1154. //
  1155. ItemChange = kItemCreate;
  1156. Info = InfoNew;
  1157. Cache.bNew = FALSE;
  1158. } else {
  1159. ItemChange = kItemName;
  1160. }
  1161. //
  1162. // Now inform the UI that the name has changed. We can safely
  1163. // send the notification before updating our internal data
  1164. // (by calling vUpdateInfoData) since the client guarantees
  1165. // thread synchronization.
  1166. //
  1167. // We called vItemChanged before vUpdateInfoData since we
  1168. // need both the old and new name.
  1169. //
  1170. _pDataClient->vItemChanged( ItemChange,
  1171. (HITEM)pItemData,
  1172. Info,
  1173. InfoNew );
  1174. vUpdateInfoData( pData, Table, pInfo );
  1175. return FALSE;
  1176. }
  1177. default:
  1178. //
  1179. // Update the standard data item.
  1180. //
  1181. vUpdateInfoData( pData, Table, pInfo );
  1182. //
  1183. // Problem: we don't know when the printer should be removed,
  1184. // since there is no "PRINTER_STATUS_DELETED" message that
  1185. // indicates there will be no more printer notifications.
  1186. //
  1187. // Normally we'd just look for a printer that is pending
  1188. // deletion and has zero jobs, but this won't work since
  1189. // we get printer status _before_ CJobs. Therefore, we
  1190. // must pre-initialize CJobs to be some large value like -1.
  1191. //
  1192. // Determine whether an item should be deleted from the UI.
  1193. // If it's pending deletion and has no jobs, it should be removed.
  1194. //
  1195. if( pItemData->_aInfo[kIndexStatus].dwData &
  1196. PRINTER_STATUS_PENDING_DELETION &&
  1197. pItemData->_aInfo[kIndexCJobs].dwData == 0 ){
  1198. ItemChange = kItemDelete;
  1199. } else {
  1200. //
  1201. // Distinguish between an info change (affects icon/list view)
  1202. // vs. an attribute change (affects report view, but not icon/
  1203. // list view).
  1204. //
  1205. ItemChange = ( pData->Field == PRINTER_NOTIFY_FIELD_ATTRIBUTES ) ?
  1206. kItemInfo :
  1207. kItemAttributes;
  1208. //
  1209. // Make the port name change an info change similar to the
  1210. // sharing attribute change. ( affects icon/list view )
  1211. //
  1212. if( pData->Field == PRINTER_NOTIFY_FIELD_PORT_NAME )
  1213. ItemChange = kItemInfo;
  1214. //
  1215. // If this is a security notification then map to the correct
  1216. // kItemSecurity id.
  1217. //
  1218. if( pData->Field == PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR )
  1219. ItemChange = kItemSecurity;
  1220. }
  1221. //
  1222. // Now inform the UI that something changed.
  1223. //
  1224. Info.NaturalIndex = Cache.NaturalIndex;
  1225. _pDataClient->vItemChanged( ItemChange,
  1226. (HITEM)pItemData,
  1227. Info,
  1228. Info );
  1229. break;
  1230. }
  1231. return ItemChange == kItemDelete;
  1232. }
  1233. /********************************************************************
  1234. Debug support
  1235. ********************************************************************/
  1236. #if DBG
  1237. //
  1238. // Define debug tables.
  1239. //
  1240. UINT aFieldMax[2] = {
  1241. I_PRINTER_END,
  1242. I_JOB_END
  1243. };
  1244. //
  1245. // Define debug strings for fields.
  1246. //
  1247. #define DEFINE( field, attrib, table, y, z )\
  1248. #field,
  1249. LPSTR aszFieldJob[] = {
  1250. #include "ntfyjob.h"
  1251. NULL
  1252. };
  1253. LPSTR aszFieldPrinter[] = {
  1254. #include "ntfyprn.h"
  1255. NULL
  1256. };
  1257. #undef DEFINE
  1258. LPSTR* aaszField[2] = {
  1259. aszFieldPrinter,
  1260. aszFieldJob
  1261. };
  1262. PCSTR acszType[2] = {
  1263. "Printer: ",
  1264. "Job : "
  1265. };
  1266. VOID
  1267. VDataNotify::
  1268. vDbgOutputInfo(
  1269. IN const PPRINTER_NOTIFY_INFO pInfo
  1270. )
  1271. /*++
  1272. Routine Description:
  1273. Dump out the pInfo data to the debugger.
  1274. Arguments:
  1275. pInfo - Info to dump.
  1276. Return Value:
  1277. --*/
  1278. {
  1279. PPRINTER_NOTIFY_INFO_DATA pData;
  1280. DWORD i, j;
  1281. DWORD Type;
  1282. DWORD Field;
  1283. BOOL bFound = FALSE;
  1284. for( i = 0, pData = pInfo->aData; i < pInfo->Count; i++, pData++ ){
  1285. Type = pData->Type;
  1286. Field = pData->Field;
  1287. //
  1288. // Match field to I_ index.
  1289. //
  1290. for( j=0; j< aFieldMax[Type]; j++ ){
  1291. if( j == Field ) {
  1292. bFound = TRUE;
  1293. break;
  1294. }
  1295. }
  1296. if( !bFound ){
  1297. DBGMSG( DBG_MIN, ( "[?Field 0x%x not found (Type=%d)\n", Field, Type ));
  1298. continue;
  1299. }
  1300. switch( aaTable[Type][j] ){
  1301. case TABLE_STRING:
  1302. DBGMSG( DBG_MIN,
  1303. ( "> %hs Id = 0x%x | %hs = "TSTR"\n",
  1304. acszType[Type], pData->Id, aaszField[Type][j],
  1305. pData->NotifyData.Data.pBuf ));
  1306. if( !((LPTSTR)pData->NotifyData.Data.pBuf)[0] ){
  1307. DBGMSG( DBG_MIN|DBG_NOHEAD, ( "\n" ));
  1308. }
  1309. break;
  1310. case TABLE_DWORD:
  1311. DBGMSG( DBG_MIN,
  1312. ( "> %hs Id = 0x%x | %hs = 0x%x = %d\n",
  1313. acszType[Type], pData->Id, aaszField[Type][j],
  1314. pData->NotifyData.adwData[0],
  1315. pData->NotifyData.adwData[0] ));
  1316. break;
  1317. case TABLE_TIME:
  1318. {
  1319. SYSTEMTIME LocalTime;
  1320. TCHAR szTime[80];
  1321. TCHAR szDate[80];
  1322. LCID lcid = GetUserDefaultLCID();
  1323. if ( !SystemTimeToTzSpecificLocalTime(
  1324. NULL,
  1325. (PSYSTEMTIME)pData->NotifyData.Data.pBuf,
  1326. &LocalTime )) {
  1327. DBGMSG( DBG_MIN,
  1328. ( "[SysTimeToTzSpecLocalTime failed %d]\n",
  1329. GetLastError( )));
  1330. break;
  1331. }
  1332. if( !GetTimeFormat( lcid,
  1333. 0,
  1334. &LocalTime,
  1335. NULL,
  1336. szTime,
  1337. COUNTOF( szTime ))){
  1338. DBGMSG( DBG_MIN,
  1339. ( "[No Time %d], ", GetLastError( )));
  1340. break;
  1341. }
  1342. if( !GetDateFormat( lcid,
  1343. 0,
  1344. &LocalTime,
  1345. NULL,
  1346. szDate,
  1347. COUNTOF( szDate ))) {
  1348. DBGMSG( DBG_MIN, ( "[No Date %d]\n", GetLastError( )));
  1349. break;
  1350. }
  1351. DBGMSG( DBG_MIN,
  1352. ( "> %hs Id = 0x%x | %hs = "TSTR" "TSTR"\n",
  1353. acszType[Type], pData->Id, aaszField[Type][j],
  1354. szTime,
  1355. szDate ));
  1356. break;
  1357. }
  1358. default:
  1359. DBGMSG( DBG_MIN,
  1360. ( "[?tab %d: t_0x%x f_0x%x %x, %x]\n",
  1361. aaTable[Type][j],
  1362. Type,
  1363. Field,
  1364. pData->NotifyData.adwData[0],
  1365. pData->NotifyData.adwData[1] ));
  1366. break;
  1367. }
  1368. }
  1369. }
  1370. #endif