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.

1559 lines
48 KiB

  1. /*++
  2. Copyright (c) 1990 - 1996 Microsoft Corporation
  3. Module Name:
  4. schedule.c
  5. Abstract:
  6. This module provides all the scheduling services for the Local Spooler
  7. Author:
  8. Dave Snipp (DaveSn) 15-Mar-1991
  9. Revision History:
  10. Krishna Ganugapati (KrishnaG) 07-Dec-1993 - rewrote the scheduler thread to
  11. gracefully kill off port threads if there are no jobs assigned to ports and
  12. to recreate the port thread if the port receives a job and is without a thread.
  13. Matthew A Felton (MattFe) June 1994 RapidPrint implemented
  14. MattFe April 96 Chained Jobs
  15. --*/
  16. #include <precomp.h>
  17. #include "filepool.hxx"
  18. #define MIDNIGHT (60 * 60 * 24)
  19. //
  20. // Ten minutes, seconds are multiplied in by the Scheduler code.
  21. //
  22. #define FPTIMEOUT (60 * 10)
  23. #if DBG
  24. /* For the debug message:
  25. */
  26. #define HOUR_FROM_SECONDS(Time) (((Time) / 60) / 60)
  27. #define MINUTE_FROM_SECONDS(Time) (((Time) / 60) % 60)
  28. #define SECOND_FROM_SECONDS(Time) (((Time) % 60) % 60)
  29. /* Format for %02d:%02d:%02d replaceable string:
  30. */
  31. #define FORMAT_HOUR_MIN_SEC(Time) HOUR_FROM_SECONDS(Time), \
  32. MINUTE_FROM_SECONDS(Time), \
  33. SECOND_FROM_SECONDS(Time)
  34. /* Format for %02d:%02d replaceable string:
  35. */
  36. #define FORMAT_HOUR_MIN(Time) HOUR_FROM_SECONDS(Time), \
  37. MINUTE_FROM_SECONDS(Time)
  38. #endif
  39. HANDLE SchedulerSignal = NULL;
  40. HANDLE PowerManagementSignal = NULL;
  41. VOID
  42. DbgPrintTime(
  43. );
  44. DWORD
  45. GetTimeToWait(
  46. DWORD CurrentTime,
  47. PINIPRINTER pIniPrinter,
  48. PINIJOB pIniJob
  49. );
  50. DWORD
  51. GetCurrentTimeInSeconds(
  52. VOID
  53. );
  54. VOID
  55. InitializeSchedulingGlobals(
  56. );
  57. VOID
  58. CheckMemoryAvailable(
  59. PINIJOB *ppIniJob,
  60. BOOL bFixedJob
  61. );
  62. VOID
  63. UpdateJobList(
  64. );
  65. BOOL
  66. AddToJobList(
  67. PINIJOB pIniJob,
  68. SIZE_T Required,
  69. DWORD dwJobList
  70. );
  71. BOOL
  72. SchedulerCheckPort(
  73. PINISPOOLER pIniSpooler,
  74. PINIPORT pIniPort,
  75. PINIJOB pFixedIniJob,
  76. PDWORD pdwSchedulerTimeout
  77. );
  78. BOOL
  79. SchedulerCheckSpooler(
  80. PINISPOOLER pIniSpooler,
  81. PDWORD pdwSchedulerTimeout
  82. );
  83. BOOL
  84. GetJobFromWaitingList(
  85. PINIPORT *ppIniPort,
  86. PINIJOB *ppIniJob,
  87. DWORD dwPriority
  88. );
  89. #if _MSC_FULL_VER >= 13008827
  90. #pragma warning(push)
  91. #pragma warning(disable:4715) // Not all control paths return (due to infinite loop)
  92. #endif
  93. DWORD
  94. SchedulerThread(
  95. PINISPOOLER pIniSpooler
  96. )
  97. {
  98. DWORD SchedulerTimeout = INFINITE; // In seconds
  99. PINISPOOLER pIniSpoolerNext;
  100. BOOL bJobScheduled = FALSE;
  101. HANDLE hTempFP = INVALID_HANDLE_VALUE;
  102. //
  103. // Initialize the EMF scheduling parameters
  104. //
  105. InitializeSchedulingGlobals();
  106. for( ; ; ) {
  107. if (SchedulerTimeout == INFINITE) {
  108. DBGMSG(DBG_TRACE, ("Scheduler thread waiting indefinitely\n"));
  109. } else {
  110. DBGMSG(DBG_TRACE, ("Scheduler thread waiting for %02d:%02d:%02d\n",
  111. FORMAT_HOUR_MIN_SEC(SchedulerTimeout)));
  112. //
  113. // The SchedulerTimeout is in seconds, so we need to multiply
  114. // by 1000.
  115. //
  116. SchedulerTimeout *= 1000;
  117. }
  118. if (WaitForSingleObject(SchedulerSignal, SchedulerTimeout) == WAIT_FAILED) {
  119. DBGMSG(DBG_WARNING, ("SchedulerThread:WaitforSingleObject failed: Error %d\n",
  120. GetLastError()));
  121. }
  122. if (WaitForSingleObject(PowerManagementSignal, INFINITE) == WAIT_FAILED)
  123. {
  124. DBGMSG(DBG_WARNING, ("SchedulerThread:WaitforSingleObject failed on ACPI event: Error %d\n",
  125. GetLastError()));
  126. }
  127. /* The timeout will be reset if there are jobs to be printed
  128. * at a later time. This will result in WaitForSingleObject
  129. * timing out when the first one is due to be printed.
  130. */
  131. SchedulerTimeout = INFINITE;
  132. bJobScheduled = FALSE;
  133. EnterSplSem();
  134. INCSPOOLERREF( pLocalIniSpooler );
  135. for(pIniSpooler = pLocalIniSpooler;
  136. pIniSpooler;
  137. pIniSpooler = pIniSpoolerNext ){
  138. //
  139. // Only schedule check spoolers that are local.
  140. //
  141. if( pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL ){
  142. bJobScheduled = (SchedulerCheckSpooler( pIniSpooler, &SchedulerTimeout )
  143. || bJobScheduled);
  144. //
  145. // FP Change
  146. // Trim the filepool.
  147. //
  148. if (pIniSpooler &&
  149. (hTempFP = pIniSpooler->hFilePool) != INVALID_HANDLE_VALUE &&
  150. !bJobScheduled )
  151. {
  152. //
  153. // We've incremented the spooler Refcount, so we can
  154. // safely leave the splsem.
  155. //
  156. LeaveSplSem();
  157. if (TrimPool(hTempFP))
  158. {
  159. if (SchedulerTimeout == INFINITE)
  160. {
  161. SchedulerTimeout = FPTIMEOUT;
  162. }
  163. }
  164. EnterSplSem();
  165. }
  166. }
  167. pIniSpoolerNext = pIniSpooler->pIniNextSpooler;
  168. if( pIniSpoolerNext ){
  169. INCSPOOLERREF( pIniSpoolerNext );
  170. }
  171. DECSPOOLERREF( pIniSpooler );
  172. }
  173. LeaveSplSem();
  174. }
  175. return 0;
  176. }
  177. #if _MSC_FULL_VER >= 13008827
  178. #pragma warning(pop)
  179. #endif
  180. BOOL
  181. SchedulerCheckPort(
  182. PINISPOOLER pIniSpooler,
  183. PINIPORT pIniPort,
  184. PINIJOB pFixedIniJob,
  185. PDWORD pdwSchedulerTimeout)
  186. /*++
  187. Function Description: Checks if pIniJob can be assigned to pIniPort. If pInijob is NULL we
  188. search for another job that can print on pIniPort. The job (if any) is
  189. scheduled and the dwSchedulerTimeout is adjusted for the next waiting
  190. job
  191. Parameters: pIniSpooler -- pointer to INISPOOLER struct
  192. pIniPort -- Port to a assign a job to
  193. pFixedIniJob -- Assign this job, if possible. If NULL search for other jobs
  194. pdwSchedulerTimeOut -- How much time will the scheduler thread sleep
  195. Return Values: TRUE if a job gets assigned to pIniPort
  196. FALSE otherwise
  197. --*/
  198. {
  199. BOOL bFixedJob, bReturn = FALSE;
  200. PINIJOB pIniJob = NULL;
  201. DWORD ThisPortTimeToWait; // In seconds
  202. DWORD CurrentTickCount;
  203. // Check of there is a pre assigned job
  204. bFixedJob = pFixedIniJob ? TRUE : FALSE;
  205. DBGMSG(DBG_TRACE, ("Now Processing Port %ws\n", pIniPort->pName));
  206. SPLASSERT( pIniPort->signature == IPO_SIGNATURE );
  207. // Check conditions based on which we can assign this
  208. // port a job.
  209. // Rule 1 - if there is a job being processed by this
  210. // port, then leave this port alone.
  211. if ( (pIniPort->pIniJob) &&
  212. !(pIniPort->Status & PP_WAITING )){
  213. SPLASSERT( pIniPort->pIniJob->signature == IJ_SIGNATURE );
  214. //
  215. // If this port has a job which has timed out AND
  216. // there is another job waiting on this port then
  217. // push the timed out job out by setting JOB_ABANDON
  218. // see spooler.c LocalReadPrinter
  219. //
  220. pIniJob = pIniPort->pIniJob;
  221. if (( pIniJob->Status & JOB_TIMEOUT ) &&
  222. ( pIniJob->WaitForWrite != NULL ) &&
  223. ( NULL != AssignFreeJobToFreePort( pIniPort, &ThisPortTimeToWait ) )) {
  224. INCPORTREF( pIniPort );
  225. INCJOBREF( pIniJob );
  226. InterlockedOr((LONG*)&(pIniJob->Status), JOB_ABANDON);
  227. ReallocSplStr(&pIniJob->pStatus, szFastPrintTimeout);
  228. LogJobInfo( pIniSpooler,
  229. MSG_DOCUMENT_TIMEOUT,
  230. pIniJob->JobId,
  231. pIniJob->pDocument,
  232. pIniJob->pUser,
  233. pIniJob->pIniPrinter->pName,
  234. dwFastPrintWaitTimeout );
  235. SetEvent( pIniJob->WaitForWrite );
  236. SetPrinterChange(pIniJob->pIniPrinter,
  237. pIniJob,
  238. NVJobStatusAndString,
  239. PRINTER_CHANGE_SET_JOB,
  240. pIniJob->pIniPrinter->pIniSpooler);
  241. DECJOBREF( pIniJob );
  242. DECPORTREF( pIniPort );
  243. }
  244. return bReturn;
  245. }
  246. if (bFixedJob) {
  247. // Use a pre-assigned job
  248. pIniJob = pFixedIniJob;
  249. } else {
  250. // Is there any job that can be scheduled to this port ?
  251. pIniJob = AssignFreeJobToFreePort(pIniPort, &ThisPortTimeToWait);
  252. *pdwSchedulerTimeout = min(ThisPortTimeToWait, *pdwSchedulerTimeout);
  253. }
  254. if (pIniPort->Status & PP_THREADRUNNING ) {
  255. if (pIniPort->Status & PP_WAITING) {
  256. //
  257. // If we are working on a Chained Job then the job
  258. // has already been assigned by the port thread from
  259. // the last job on this port so ignore any other job
  260. // found for us.
  261. //
  262. if (pIniPort->pIniJob) {
  263. if (bFixedJob && (pIniJob != pIniPort->pIniJob)) {
  264. //
  265. // The fixed job could not assigned because chained jobs
  266. // must be printed sequentially
  267. //
  268. pIniJob = NULL;
  269. } else {
  270. pIniJob = pIniPort->pIniJob;
  271. DBGMSG( DBG_TRACE, ("ScheduleThread NextJob pIniPort %x JoId %d pIniJob %x\n",
  272. pIniPort, pIniJob->JobId, pIniJob ));
  273. }
  274. }
  275. //
  276. // If the delay in scheduling has been requested by FlushPrinter wait until
  277. // IdleTime elapses
  278. //
  279. // We're using a local here to avoid multiple calls to GetTickCount().
  280. //
  281. CurrentTickCount = GetTickCount();
  282. if (pIniPort->bIdleTimeValid && (int)(pIniPort->IdleTime - CurrentTickCount) > 0) {
  283. //
  284. // Our port is not ready to accept a job just yet, we need to
  285. // remind the Scheduler to wake up in a little while to reassign
  286. // the job to the port.
  287. //
  288. // The difference is in milliseconds, so we divide by 1000 to get to
  289. // seconds, and add 1 to make sure we return after the timeout has
  290. // expired.
  291. //
  292. *pdwSchedulerTimeout =
  293. min( ((pIniPort->IdleTime - CurrentTickCount)/1000) + 1,
  294. *pdwSchedulerTimeout);
  295. //
  296. // Null out the job so we don't assign it to the port.
  297. //
  298. pIniJob = NULL;
  299. }
  300. else {
  301. pIniPort->bIdleTimeValid = FALSE;
  302. }
  303. if ( pIniJob ) {
  304. CheckMemoryAvailable( &pIniJob, bFixedJob );
  305. }
  306. if ( pIniJob ) {
  307. DBGMSG(DBG_TRACE, ("ScheduleThread pIniJob %x Size %d pDocument %ws\n",
  308. pIniJob, pIniJob->Size, DBGSTR( pIniJob->pDocument)));
  309. if (pIniPort != pIniJob->pIniPort) {
  310. ++pIniPort->cJobs;
  311. pIniJob->pIniPort = pIniPort;
  312. }
  313. pIniPort->pIniJob = pIniJob;
  314. //
  315. // We have a new job on this port, make sure the Critical Section mask is
  316. // cleared.
  317. //
  318. pIniPort->InCriticalSection = 0;
  319. if( !pIniJob->pCurrentIniJob ){
  320. //
  321. // If pCurrentIniJob is NULL, then this is
  322. // beginning of a new job (single or linked).
  323. //
  324. // Clustered spoolers are interested in the
  325. // number of jobs that are actually printing.
  326. // We need to know when all printing jobs are
  327. // done so we can shutdown.
  328. //
  329. ++pIniJob->pIniPrinter->pIniSpooler->cFullPrintingJobs;
  330. if( pIniJob->NextJobId ){
  331. //
  332. // Chained Jobs
  333. // Point the Master Jobs Current Pointer to
  334. // the first in the chain.
  335. //
  336. pIniJob->pCurrentIniJob = pIniJob;
  337. }
  338. }
  339. pIniPort->Status &= ~PP_WAITING;
  340. //
  341. // If the job is still spooling then we will need
  342. // to create an event to synchronize the port thread
  343. //
  344. if ( !( pIniJob->Status & JOB_DIRECT ) ) {
  345. pIniJob->WaitForWrite = NULL;
  346. if ( pIniJob->Status & JOB_SPOOLING ) {
  347. pIniJob->WaitForWrite = CreateEvent(NULL,
  348. EVENT_RESET_MANUAL,
  349. EVENT_INITIAL_STATE_NOT_SIGNALED,
  350. NULL);
  351. }
  352. }
  353. //
  354. // Update cRef so that nobody can delete this job
  355. // before the Port Thread Starts up
  356. //
  357. SplInSem();
  358. INCJOBREF(pIniJob);
  359. SetEvent(pIniPort->Semaphore);
  360. InterlockedOr((LONG*)&(pIniJob->Status), JOB_DESPOOLING);
  361. bReturn = TRUE;
  362. } else {
  363. //
  364. // If the port thread is running and it is waiting
  365. // for a job and there is no job to assign, then
  366. // kill the port thread
  367. //
  368. DBGMSG(DBG_TRACE, ("Now destroying the new port thread %.8x\n", pIniPort));
  369. DestroyPortThread(pIniPort, FALSE);
  370. pIniPort->Status &= ~PP_WAITING;
  371. if (pIniPort->Status & PP_FILE) {
  372. //
  373. // We should destroy the Pseudo-File Port at this
  374. // point. There are no jobs assigned to this Port
  375. // and we are in Critical Section
  376. //
  377. //
  378. // Now deleting the pIniPort entry for the Pseudo-Port
  379. //
  380. DBGMSG(DBG_TRACE, ("Now deleting the Pseudo-Port %ws\n", pIniPort->pName));
  381. if ( !pIniPort->cJobs )
  382. DeletePortEntry(pIniPort);
  383. return bReturn;
  384. }
  385. }
  386. }
  387. } else if (!(pIniPort->Status & PP_THREADRUNNING) && pIniJob) {
  388. //
  389. // If the port thread is not running, and there is a job to
  390. // assign, then create a port thread. REMEMBER do not assign
  391. // the job to the port because we are in a Spooler Section and
  392. // if we release the Spooler Section, the first thing the port
  393. // thread does is reinitialize its pIniPort->pIniJob to NULL
  394. // Wait the next time around we execute the for loop to assign
  395. // the job to this port. Should we set *pdwSchedulerTimeOut to zero??
  396. //
  397. DBGMSG( DBG_TRACE, ("ScheduleThread Now creating the new port thread pIniPort %x\n", pIniPort));
  398. CreatePortThread( pIniPort );
  399. bReturn = TRUE;
  400. }
  401. return bReturn;
  402. }
  403. BOOL
  404. SchedulerCheckSpooler(
  405. PINISPOOLER pIniSpooler,
  406. PDWORD pdwSchedulerTimeout)
  407. /*++
  408. Function Description: This function assigns a waiting job to a port after every minute.
  409. If memory is available it schedules as many jobs from the waiting
  410. list as possible.
  411. It then loops thru the ports in a round-robin fashion scheduling jobs
  412. or adding them to the waiting list.
  413. Parameters: pIniSpooler -- pointer to the INISPOOLER struct
  414. pdwSchedulerTimeout -- duration of time for which the scheduler
  415. thread will sleep
  416. Return Values: NONE
  417. --*/
  418. {
  419. DWORD ThisPortTimeToWait = INFINITE; // In seconds
  420. DWORD dwTickCount;
  421. PINIPORT pIniPort;
  422. PINIJOB pIniJob;
  423. PINIPORT pIniNextPort = NULL;
  424. BOOL bJobScheduled = FALSE;
  425. UpdateJobList();
  426. //
  427. // If Jobs have been waiting for 1 minute and nothing has been scheduled in
  428. // that time, schedule one of the waiting jobs.
  429. //
  430. dwTickCount = GetTickCount();
  431. if (pWaitingList &&
  432. ((dwTickCount - pWaitingList->dwWaitTime) > ONE_MINUTE) &&
  433. ((dwTickCount - dwLastScheduleTime) > ONE_MINUTE)) {
  434. if (GetJobFromWaitingList(&pIniPort, &pIniJob, SPL_FIRST_JOB)) {
  435. bJobScheduled = (SchedulerCheckPort(pIniSpooler, pIniPort, pIniJob, &ThisPortTimeToWait)
  436. || bJobScheduled);
  437. *pdwSchedulerTimeout = min(*pdwSchedulerTimeout, ThisPortTimeToWait);
  438. }
  439. }
  440. //
  441. // Use the available memory to schedule waiting jobs
  442. //
  443. while (GetJobFromWaitingList(&pIniPort, &pIniJob, SPL_USE_MEMORY)) {
  444. bJobScheduled = (SchedulerCheckPort(pIniSpooler, pIniPort, pIniJob, &ThisPortTimeToWait)
  445. || bJobScheduled);
  446. *pdwSchedulerTimeout = min(*pdwSchedulerTimeout, ThisPortTimeToWait);
  447. }
  448. //
  449. // Loop thru the ports and get the list of jobs that can be scheduled
  450. //
  451. for (pIniPort = pIniSpooler->pIniPort;
  452. pIniPort;
  453. pIniPort = pIniNextPort) {
  454. pIniNextPort = pIniPort->pNext;
  455. //
  456. // SchedulerCheckPort can leave the critical section and the iniPort can
  457. // be removed from the list in the meanwhile. So, maintain the Ref on it.
  458. //
  459. if (pIniNextPort) {
  460. INCPORTREF(pIniNextPort);
  461. }
  462. bJobScheduled = (SchedulerCheckPort(pIniSpooler, pIniPort, NULL, &ThisPortTimeToWait)
  463. || bJobScheduled);
  464. *pdwSchedulerTimeout = min(*pdwSchedulerTimeout, ThisPortTimeToWait);
  465. if (pIniNextPort) {
  466. DECPORTREF(pIniNextPort);
  467. }
  468. }
  469. // If there any jobs left try to reschedule latest after one minute.
  470. if (pWaitingList) {
  471. *pdwSchedulerTimeout = min(*pdwSchedulerTimeout, 60);
  472. }
  473. return bJobScheduled;
  474. }
  475. VOID
  476. InitializeSchedulingGlobals(
  477. )
  478. /*++
  479. Function Description: Initializes globals used for EMF scheduling
  480. Parameters: NONE
  481. Return Values: NONE
  482. --*/
  483. {
  484. MEMORYSTATUS msBuffer;
  485. HKEY hPrintRegKey = NULL;
  486. DWORD dwType, dwData, dwcbData;
  487. bUseEMFScheduling = TRUE; // default value
  488. dwcbData = sizeof(DWORD);
  489. // Check the registry for the flag for turning off EMF scheduling. If the
  490. // key is not present/Reg Apis fail default to using the scheduling.
  491. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  492. szRegistryRoot,
  493. 0,
  494. KEY_READ,
  495. &hPrintRegKey) == ERROR_SUCCESS) {
  496. if (RegQueryValueEx(hPrintRegKey,
  497. szEMFThrottle,
  498. NULL,
  499. &dwType,
  500. (LPBYTE) &dwData,
  501. &dwcbData) == ERROR_SUCCESS) {
  502. if (dwData == 0) {
  503. // Scheduling has been turned off
  504. bUseEMFScheduling = FALSE;
  505. }
  506. }
  507. }
  508. // Get the memory status
  509. GlobalMemoryStatus(&msBuffer);
  510. // Use half the physical memory in megabytes
  511. TotalMemoryForRendering = msBuffer.dwTotalPhys / ( 2048 * 1024);
  512. AvailMemoryForRendering = TotalMemoryForRendering;
  513. dwNumberOfEMFJobsRendering = 0;
  514. pWaitingList = NULL;
  515. pScheduleList = NULL;
  516. dwLastScheduleTime = GetTickCount();
  517. if (hPrintRegKey) {
  518. RegCloseKey(hPrintRegKey);
  519. }
  520. return;
  521. }
  522. DWORD
  523. GetMemoryEstimate(
  524. LPDEVMODE pDevMode
  525. )
  526. /*++
  527. Function Description: Computes a rough estimate of the memory required for rendering a
  528. single page based on the DPI and color settings
  529. Parameters: pDevMode -- pointer to the devmode of the job
  530. Return Values: Memory estimate
  531. --*/
  532. {
  533. DWORD dwRequired, dwXRes, dwYRes, dwMaxRes;
  534. DWORD dwXIndex, dwYIndex;
  535. DWORD MemHeuristic[3][2] = {{8 , 4},
  536. {12, 6},
  537. {16, 8}};
  538. //
  539. // Get the max resolution on either axis
  540. //
  541. dwXRes = dwYRes = 300;
  542. if (pDevMode) {
  543. if (pDevMode->dmFields & DM_PRINTQUALITY) {
  544. switch (pDevMode->dmPrintQuality) {
  545. case DMRES_DRAFT:
  546. case DMRES_LOW:
  547. case DMRES_MEDIUM:
  548. dwXRes = dwYRes = 300;
  549. break;
  550. case DMRES_HIGH:
  551. dwXRes = dwYRes = 600;
  552. break;
  553. default:
  554. dwXRes = dwYRes = (DWORD) pDevMode->dmPrintQuality;
  555. break;
  556. }
  557. }
  558. if (pDevMode->dmFields & DM_YRESOLUTION) {
  559. dwYRes = (DWORD)pDevMode->dmYResolution;
  560. }
  561. }
  562. dwMaxRes = (dwXRes >= dwYRes) ? dwXRes : dwYRes;
  563. if (dwMaxRes <= 300) {
  564. dwXIndex = 0;
  565. } else if (dwMaxRes <= 600) {
  566. dwXIndex = 1;
  567. } else {
  568. dwXIndex = 2;
  569. }
  570. // Get the color setting
  571. dwYIndex = 1;
  572. if (pDevMode) {
  573. if ((pDevMode->dmFields & DM_COLOR) &&
  574. (pDevMode->dmColor == DMCOLOR_COLOR)) {
  575. dwYIndex = 0;
  576. }
  577. }
  578. dwRequired = MemHeuristic[dwXIndex][dwYIndex];
  579. return dwRequired;
  580. }
  581. VOID
  582. CheckMemoryAvailable(
  583. PINIJOB *ppIniJob,
  584. BOOL bFixedJob
  585. )
  586. /*++
  587. Function Description: Checks for availability of memory required for rendering the
  588. job. Performs some scheduling based on resource requirements.
  589. Parameters: ppIniJob - pointer to the PINIJOB to be scheduled
  590. bFixedJob - flag to disable memory requirement checks
  591. Return Values: NONE
  592. --*/
  593. {
  594. PINIJOB pIniJob;
  595. SIZE_T Required;
  596. SplInSem();
  597. if (ppIniJob) {
  598. pIniJob = *ppIniJob;
  599. } else {
  600. //
  601. // should not happen
  602. //
  603. return;
  604. }
  605. //
  606. // Dont use scheduling algorithm if it has been explicitly turned off
  607. //
  608. if (!bUseEMFScheduling) {
  609. return;
  610. }
  611. //
  612. // Don't use scheduling algorithm for non EMF jobs
  613. //
  614. if (!pIniJob->pDatatype ||
  615. (wstrcmpEx(pIniJob->pDatatype, gszNT4EMF, FALSE) &&
  616. wstrcmpEx(pIniJob->pDatatype, L"NT EMF 1.006", FALSE) &&
  617. wstrcmpEx(pIniJob->pDatatype, L"NT EMF 1.007", FALSE) &&
  618. wstrcmpEx(pIniJob->pDatatype, gszNT5EMF, FALSE)) ) {
  619. return;
  620. }
  621. Required = GetMemoryEstimate(pIniJob->pDevMode);
  622. if (bFixedJob) {
  623. // This job has to be assigned without memory availability checks
  624. RemoveFromJobList(pIniJob, JOB_WAITING_LIST);
  625. AddToJobList(pIniJob, Required, JOB_SCHEDULE_LIST);
  626. return;
  627. }
  628. // Check if the job has to wait, based on
  629. // 1. Some jobs are already waiting OR
  630. // 2. There is insufficient memory available due to currently rendering jobs
  631. if ((pWaitingList != NULL) ||
  632. ((AvailMemoryForRendering < Required) &&
  633. (dwNumberOfEMFJobsRendering > 0))) {
  634. AddToJobList(pIniJob, Required, JOB_WAITING_LIST);
  635. *ppIniJob = NULL;
  636. return;
  637. }
  638. // The job can be scheduled right away
  639. AddToJobList(pIniJob, Required, JOB_SCHEDULE_LIST);
  640. return;
  641. }
  642. PINIJOB
  643. AssignFreeJobToFreePort(
  644. PINIPORT pIniPort,
  645. DWORD *pSecsToWait
  646. )
  647. /*++
  648. Note: You must ensure that the port is free. This function will not
  649. assign a job to this port, but if there exists one, it will return a
  650. pointer to the INIJOB. Irrespective of whether it finds a job or not,
  651. it will return the minimum timeout value that the scheduler thread
  652. should sleep for.
  653. --*/
  654. {
  655. DWORD CurrentTime; // Time in seconds
  656. DWORD Timeout = INFINITE; // Time in seconds
  657. DWORD SecsToWait; // Time in seconds
  658. PINIPRINTER pTopIniPrinter, pIniPrinter;
  659. PINIJOB pTopIniJob, pIniJob;
  660. PINIJOB pTopIniJobOnThisPrinter, pTopIniJobSpooling;
  661. DWORD i;
  662. SplInSem();
  663. if( pIniPort->Status & PP_ERROR ){
  664. *pSecsToWait = INFINITE;
  665. return NULL;
  666. }
  667. pTopIniPrinter = NULL;
  668. pTopIniJob = NULL;
  669. for (i = 0; i < pIniPort->cPrinters ; i++) {
  670. pIniPrinter = pIniPort->ppIniPrinter[i];
  671. //
  672. // if this printer is in a state not to print skip it
  673. //
  674. if ( PrinterStatusBad(pIniPrinter->Status) ||
  675. (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE) ) {
  676. continue;
  677. }
  678. //
  679. // if we haven't found a top-priority printer yet,
  680. // or this printer is higher priority than the top-priority
  681. // printer, see if it has jobs to go. If we find any, the
  682. // highest priority one will become the top priority job and
  683. // this printer will become the top-priority printer.
  684. //
  685. if (!pTopIniPrinter ||
  686. (pIniPrinter->Priority > pTopIniPrinter->Priority)) {
  687. pTopIniJobOnThisPrinter = NULL;
  688. pTopIniJobSpooling = NULL;
  689. pIniJob = pIniPrinter->pIniFirstJob;
  690. while (pIniJob) {
  691. if (!(pIniPort->Status & PP_FILE) &&
  692. (pIniJob->Status & JOB_PRINT_TO_FILE)) {
  693. pIniJob = pIniJob->pIniNextJob;
  694. continue;
  695. }
  696. if ((pIniPort->Status & PP_FILE) &&
  697. !(pIniJob->Status & JOB_PRINT_TO_FILE)) {
  698. pIniJob = pIniJob->pIniNextJob;
  699. continue;
  700. }
  701. //
  702. // Make sure the spooler isn't offline.
  703. // Find a job which is not PAUSED, PRINTING etc.
  704. // Let jobs that are DIRECT & CANCELLED through
  705. // For RapidPrint also allow SPOOLING jobs to print
  706. //
  707. if (!(pIniJob->pIniPrinter->pIniSpooler->SpoolerFlags & SPL_OFFLINE) &&
  708. (!(pIniJob->Status & JOB_PENDING_DELETION) || (pIniJob->pIniPrinter->Attributes&PRINTER_ATTRIBUTE_DIRECT)) &&
  709. !(pIniJob->Status & ( JOB_PAUSED | JOB_PRINTING | JOB_COMPLETE |
  710. JOB_PRINTED | JOB_TIMEOUT |
  711. JOB_DESPOOLING | JOB_BLOCKED_DEVQ | JOB_COMPOUND )) &&
  712. ((!(pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DIRECT) &&
  713. !(pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_QUEUED)) ||
  714. !(pIniJob->Status & JOB_SPOOLING))) {
  715. //
  716. // if we find such a job, then determine how much
  717. // time, we need to wait before this job can actually
  718. // print.
  719. //
  720. CurrentTime = GetCurrentTimeInSeconds();
  721. #if DBG
  722. if (MODULE_DEBUG & DBG_TIME)
  723. DbgPrintTime();
  724. #endif
  725. SecsToWait = GetTimeToWait(CurrentTime, pIniPrinter, pIniJob);
  726. if (SecsToWait == 0) {
  727. //
  728. // if we needn't wait at all, then we make this job the
  729. // TopIniJob if either there is no TopIniJob or this job
  730. // has a higher priority than an existing TopIniJob on this
  731. // printer.
  732. //
  733. // Keep both the Highest Priority Spooling and Non
  734. // spooling job in case we want to favour non spooling
  735. // jobs over spooling jobs
  736. //
  737. if ( pIniJob->Status & JOB_SPOOLING ) {
  738. if ( pTopIniJobSpooling == NULL ) {
  739. pTopIniJobSpooling = pIniJob;
  740. } else if ( pIniJob->pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST ) {
  741. //
  742. // For DO_COMPLETE_FIRST we'll take larger jobs
  743. // first over pure priority based
  744. //
  745. if (( pIniJob->dwValidSize > pTopIniJobSpooling->dwValidSize ) ||
  746. (( pIniJob->dwValidSize == pTopIniJobSpooling->dwValidSize ) &&
  747. ( pIniJob->Priority > pTopIniJobSpooling->Priority ))) {
  748. pTopIniJobSpooling = pIniJob;
  749. }
  750. //
  751. // For Priority Based, pick a higher priority job if it has some
  752. // at least our minimum requirement
  753. //
  754. } else if (( pIniJob->Priority > pTopIniJobSpooling->Priority ) &&
  755. ( pIniJob->dwValidSize >= dwFastPrintSlowDownThreshold )) {
  756. pTopIniJobSpooling = pIniJob;
  757. }
  758. } else {
  759. if (!pTopIniJobOnThisPrinter ||
  760. (pIniJob->Status & JOB_PENDING_DELETION) ||
  761. (pIniJob->Priority > pTopIniJobOnThisPrinter->Priority)) {
  762. pTopIniJobOnThisPrinter = pIniJob;
  763. }
  764. }
  765. } else {
  766. //
  767. // if we have to wait then keep track of how long we
  768. // can doze off before the next job that is to be
  769. // scheduled later.
  770. //
  771. Timeout = min(Timeout, SecsToWait);
  772. }
  773. }
  774. //
  775. // loop thru all jobs on this printer.
  776. //
  777. pIniJob = pIniJob->pIniNextJob;
  778. }
  779. //
  780. // We've already established that this printer has a
  781. // higher priority than any previous TopIniPrinter or
  782. // that there is no TopIniPrinter yet.
  783. // if we did find a TopIniJobOnThisPrinter for this pIniPrinter
  784. // update the TopIniPrinter and TopIniJob pointers
  785. //
  786. // We don't want to schedule Spooling Jobs whose size doesn't meet
  787. // our minimum size requirement
  788. if (( pTopIniJobSpooling != NULL ) &&
  789. ( dwFastPrintSlowDownThreshold > pTopIniJobSpooling->Size )) {
  790. pTopIniJobSpooling = NULL ;
  791. }
  792. if ( pTopIniJobOnThisPrinter == NULL ) {
  793. pTopIniJobOnThisPrinter = pTopIniJobSpooling;
  794. } else {
  795. // For FastPrint we can choose to favour Completed jobs over
  796. // Spooling jobs
  797. if ( !( pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST ) &&
  798. ( pTopIniJobSpooling ) &&
  799. ( pTopIniJobSpooling->Priority >= pTopIniJobOnThisPrinter->Priority )) {
  800. pTopIniJobOnThisPrinter = pTopIniJobSpooling;
  801. }
  802. }
  803. if (pTopIniJobOnThisPrinter) {
  804. pTopIniPrinter = pIniPrinter;
  805. pTopIniJob = pTopIniJobOnThisPrinter;
  806. }
  807. }
  808. //
  809. // This ends the if clause for finding a printer with higher priority
  810. // than the current TopIniPrinter. Loop back and process all printers
  811. }
  812. //
  813. // End of For Loop for all Printers
  814. //
  815. //
  816. // if we have a TopIniJob at this stage, it means we have a job that can be
  817. // assigned to the IniPort. We will return a pointer to this job back
  818. // We will also copy the Timeout value that has been computed for this
  819. // IniPort back to the SchedulerThread.
  820. *pSecsToWait = Timeout;
  821. return(pTopIniJob);
  822. }
  823. DWORD
  824. GetCurrentTimeInSeconds(
  825. )
  826. /*++
  827. Note: This function returns a value representing the time in seconds
  828. --*/
  829. {
  830. SYSTEMTIME st;
  831. GetSystemTime(&st);
  832. return ((((st.wHour * 60) + st.wMinute) * 60) + st.wSecond);
  833. }
  834. /* GetTimeToWait
  835. *
  836. * Determines how long it is in seconds from the current time
  837. * before the specified job should be printed on the specified printer.
  838. *
  839. * Parameters:
  840. *
  841. * CurrentTime - Current system time in seconds
  842. *
  843. * pIniPrinter - Pointer to INIPRINTER structure for the printer.
  844. * This contains the StartTime and UntilTime fields.
  845. *
  846. * pIniJob - Pointer to INIJOB structure for the job.
  847. * This contains the StartTime and UntilTime fields.
  848. *
  849. * Return value:
  850. *
  851. * The number of seconds till the job should be printed.
  852. * If the job can be printed immediately, this will be 0.
  853. * We don't support specifying the day the job should be printed,
  854. * so the return value should always be in the following range:
  855. *
  856. * 0 <= return value < 86400 (60 * 60 * 24)
  857. *
  858. * Remarks:
  859. *
  860. * The user can specify hours on both the printer and the job.
  861. * Thus a printer may be configured to print only at night,
  862. * say between the hours 20:00 and 06:00.
  863. * Any job submitted to the printer outside those hours
  864. * will not print until 20:00.
  865. * If, in addition, the user specifies the hours when the job
  866. * may print (e.g. through Printer Properties -> Details
  867. * in Print Manager), the job will print when the two periods
  868. * overlap.
  869. *
  870. * This routine finds the two wait periods determined by the
  871. * printer hours and the job hours respectively.
  872. * The actual time to wait is the longer of the two.
  873. * It therefore assumes that the two periods overlap.
  874. * This doesn't matter if the routine is called again
  875. * when the scheduler thread wakes up again.
  876. *
  877. * CHANGED: 14 June 1993
  878. *
  879. * The printer times are now ignored.
  880. * When a job is submitted it inherits the printer's hours.
  881. * These are all we need to check. Now if the printer's hours
  882. * are changed, any already existing jobs on that printer
  883. * will still print within the originally assigned times.
  884. *
  885. *
  886. */
  887. DWORD
  888. GetTimeToWait(
  889. DWORD CurrentTime,
  890. PINIPRINTER pIniPrinter,
  891. PINIJOB pIniJob
  892. )
  893. {
  894. /* Printer and job start and until times are in minutes.
  895. * Convert them to seconds, so that we can start printing
  896. * bang on the minute.
  897. */
  898. DWORD PrinterStartTime = (pIniPrinter->StartTime * 60);
  899. DWORD PrinterUntilTime = (pIniPrinter->UntilTime * 60);
  900. DWORD JobStartTime = (pIniJob->StartTime * 60);
  901. DWORD JobUntilTime = (pIniJob->UntilTime * 60);
  902. DWORD PrinterTimeToWait = 0;
  903. DWORD JobTimeToWait = 0;
  904. DWORD TimeToWait = 0;
  905. /* Do the same for the job time constraints:
  906. */
  907. if (JobStartTime > JobUntilTime) {
  908. if ((CurrentTime < JobStartTime)
  909. &&(CurrentTime >= JobUntilTime)) {
  910. JobTimeToWait = (JobStartTime - CurrentTime);
  911. }
  912. } else if (JobStartTime < JobUntilTime) {
  913. if (CurrentTime < JobStartTime) {
  914. JobTimeToWait = (JobStartTime - CurrentTime);
  915. } else if (CurrentTime >= JobUntilTime) {
  916. JobTimeToWait = ((MIDNIGHT - CurrentTime) + JobStartTime);
  917. }
  918. }
  919. TimeToWait = max(PrinterTimeToWait, JobTimeToWait);
  920. DBGMSG(DBG_TRACE, ("Checking time to print %ws\n"
  921. "\tCurrent time: %02d:%02d:%02d\n"
  922. "\tPrinter hours: %02d:%02d to %02d:%02d\n"
  923. "\tJob hours: %02d:%02d to %02d:%02d\n"
  924. "\tTime to wait: %02d:%02d:%02d\n\n",
  925. pIniJob->pDocument ?
  926. pIniJob->pDocument :
  927. L"(NULL)",
  928. FORMAT_HOUR_MIN_SEC(CurrentTime),
  929. FORMAT_HOUR_MIN(PrinterStartTime),
  930. FORMAT_HOUR_MIN(PrinterUntilTime),
  931. FORMAT_HOUR_MIN(JobStartTime),
  932. FORMAT_HOUR_MIN(JobUntilTime),
  933. FORMAT_HOUR_MIN_SEC(TimeToWait)));
  934. return TimeToWait;
  935. }
  936. #if DBG
  937. VOID DbgPrintTime(
  938. )
  939. {
  940. SYSTEMTIME st;
  941. GetLocalTime(&st);
  942. DBGMSG( DBG_TIME,
  943. ( "Time: %02d:%02d:%02d\n", st.wHour, st.wMinute, st.wSecond ));
  944. }
  945. #endif
  946. VOID UpdateJobList()
  947. /*++
  948. Function Description: Remove Jobs from the scheduled list which take more than 7 minutes.
  949. This figure can be tuned up based in performance. There might be
  950. some minor wrapping up discrepencies after 49.7 days which can be
  951. safely ignored.
  952. It also removes deleted, printed and abandoned jobs from the waiting
  953. list.
  954. This function should be called inside SplSem.
  955. Parameters: NONE
  956. Return Values: NONE
  957. --*/
  958. {
  959. PJOBDATA *pJobList, pJobData;
  960. DWORD dwTickCount;
  961. SplInSem();
  962. dwTickCount = GetTickCount();
  963. pJobList = &pScheduleList;
  964. while (pJobData = *pJobList) {
  965. if ((dwTickCount - pJobData->dwScheduleTime) >= SEVEN_MINUTES) {
  966. // Dont hold up resources for this job any more.
  967. RemoveFromJobList(pJobData->pIniJob, JOB_SCHEDULE_LIST);
  968. continue;
  969. }
  970. pJobList = &(pJobData->pNext);
  971. }
  972. pJobList = &pWaitingList;
  973. while (pJobData = *pJobList) {
  974. if (pJobData->pIniJob->Status & (JOB_PRINTING | JOB_PRINTED | JOB_COMPLETE |
  975. JOB_ABANDON | JOB_PENDING_DELETION)) {
  976. RemoveFromJobList(pJobData->pIniJob, JOB_WAITING_LIST);
  977. continue;
  978. }
  979. pJobList = &(pJobData->pNext);
  980. }
  981. return;
  982. }
  983. BOOL AddToJobList(
  984. PINIJOB pIniJob,
  985. SIZE_T Required,
  986. DWORD dwJobList)
  987. /*++
  988. Function Description: This function adds pIniJob to the list specified by dwJobList. It also
  989. updates the number of rendering EMF jobs and memory available
  990. for rendering.
  991. This function should be called in SplSem.
  992. Parameters: pIniJob -- Job to be removed
  993. dwRequired -- Estimate of the memory required to render the job
  994. dwJobList -- List to add to (Waiting List or Schedule List)
  995. Return Values: TRUE if the node was added or already present
  996. FALSE otherwise
  997. --*/
  998. {
  999. PJOBDATA *pJobList, pJobData;
  1000. SIZE_T MemoryUse;
  1001. DWORD dwTickCount;
  1002. BOOL bReturn = TRUE;
  1003. SplInSem();
  1004. if (!pIniJob) {
  1005. return bReturn;
  1006. }
  1007. if (dwJobList == JOB_SCHEDULE_LIST) {
  1008. pJobList = &pScheduleList;
  1009. } else { // JOB_WAITING_LIST
  1010. pJobList = &pWaitingList;
  1011. }
  1012. while (pJobData = *pJobList) {
  1013. if (pJobData->pIniJob == pIniJob) {
  1014. // The job is already on the list. Dont add duplicates
  1015. break;
  1016. }
  1017. pJobList = &(pJobData->pNext);
  1018. }
  1019. if (!pJobData) {
  1020. // Append a new node to the list
  1021. if (pJobData = AllocSplMem(sizeof(JOBDATA))) {
  1022. pJobData->pIniJob = pIniJob;
  1023. pJobData->MemoryUse = Required;
  1024. pJobData->dwNumberOfTries = 0;
  1025. dwTickCount = GetTickCount();
  1026. if (dwJobList == JOB_SCHEDULE_LIST) {
  1027. pJobData->dwScheduleTime = dwTickCount;
  1028. pJobData->dwWaitTime = 0;
  1029. } else { // JOB_WAIT_TIME
  1030. pJobData->dwWaitTime = dwTickCount;
  1031. pJobData->dwScheduleTime = 0;
  1032. }
  1033. pJobData->pNext = *pJobList;
  1034. *pJobList = pJobData;
  1035. INCJOBREF(pIniJob);
  1036. if (dwJobList == JOB_SCHEDULE_LIST) {
  1037. // Update the scheduling globals
  1038. ++dwNumberOfEMFJobsRendering;
  1039. if (AvailMemoryForRendering > Required) {
  1040. AvailMemoryForRendering -= Required;
  1041. } else {
  1042. AvailMemoryForRendering = 0;
  1043. }
  1044. dwLastScheduleTime = dwTickCount;
  1045. }
  1046. } else {
  1047. bReturn = FALSE;
  1048. }
  1049. }
  1050. return bReturn;
  1051. }
  1052. VOID RemoveFromJobList(
  1053. PINIJOB pIniJob,
  1054. DWORD dwJobList)
  1055. /*++
  1056. Function Description: This function removes pIniJob from the list specified by dwJobList
  1057. It also updates the number of rendering EMF jobs and memory available
  1058. for rendering. The scheduler is awakened if necessary.
  1059. This function should be called inside SplSem.
  1060. Parameters: pIniJob -- Job to be removed
  1061. dwJobList -- List to remove from (Waiting List or Schedule List)
  1062. Return Values: NONE
  1063. --*/
  1064. {
  1065. PJOBDATA *pJobList, pJobData;
  1066. SIZE_T Memory;
  1067. SplInSem();
  1068. if (!pIniJob) {
  1069. return;
  1070. }
  1071. if (dwJobList == JOB_SCHEDULE_LIST) {
  1072. pJobList = &pScheduleList;
  1073. } else { // JOB_WAITING_LIST
  1074. pJobList = &pWaitingList;
  1075. }
  1076. while (pJobData = *pJobList) {
  1077. if (pJobData->pIniJob == pIniJob) {
  1078. // Remove from the list
  1079. *pJobList = pJobData->pNext;
  1080. DECJOBREF(pIniJob);
  1081. if (dwJobList == JOB_SCHEDULE_LIST) {
  1082. // Update available memory and number of rendering jobs
  1083. Memory = AvailMemoryForRendering + pJobData->MemoryUse;
  1084. AvailMemoryForRendering = min(Memory, TotalMemoryForRendering);
  1085. --dwNumberOfEMFJobsRendering;
  1086. // Awaken the scheduler since more memory if available
  1087. CHECK_SCHEDULER();
  1088. }
  1089. FreeSplMem(pJobData);
  1090. // Break since there are no duplicates in the list
  1091. break;
  1092. }
  1093. pJobList = &(pJobData->pNext);
  1094. }
  1095. return;
  1096. }
  1097. BOOL GetJobFromWaitingList(
  1098. PINIPORT *ppIniPort,
  1099. PINIJOB *ppIniJob,
  1100. DWORD dwPriority)
  1101. /*++
  1102. Function Description: This function picks up the first job in the Waiting List that can
  1103. be assigned to some free port. It should be called from within the
  1104. SplSem.
  1105. Parameters: ppIniPort - pointer to pIniPort where the job can be scheduled
  1106. ppIniJob - pointer to pIniJob which can be scheduled
  1107. dwPriority - flag to use memory availability check
  1108. Return Values: TRUE if a job can be scheduled
  1109. FALSE otherwise
  1110. --*/
  1111. {
  1112. BOOL bReturn = FALSE;
  1113. DWORD dwIndex, CurrentTime, SecsToWait;
  1114. PINIPORT pIniPort = NULL;
  1115. PINIJOB pIniJob = NULL;
  1116. PINIPRINTER pIniPrinter = NULL;
  1117. PJOBDATA pJobData;
  1118. SplInSem();
  1119. // Initialize the port and job pointers;
  1120. *ppIniPort = NULL;
  1121. *ppIniJob = NULL;
  1122. for (pJobData = pWaitingList;
  1123. pJobData;
  1124. pJobData = pJobData->pNext) {
  1125. pIniJob = pJobData->pIniJob;
  1126. pIniPrinter = pIniJob->pIniPrinter;
  1127. // Check for memory availability
  1128. if (dwPriority == SPL_USE_MEMORY) {
  1129. if ((pJobData->MemoryUse > AvailMemoryForRendering) &&
  1130. (dwNumberOfEMFJobsRendering != 0)) {
  1131. // Insufficient memory
  1132. continue;
  1133. }
  1134. } else { // SPL_FIRST_JOB
  1135. if (pJobData->dwNumberOfTries > 2) {
  1136. continue;
  1137. }
  1138. }
  1139. // If this job can't be printed, go to the next one
  1140. if (pIniJob->Status & ( JOB_PAUSED | JOB_PRINTING |
  1141. JOB_PRINTED | JOB_TIMEOUT |
  1142. JOB_DESPOOLING | JOB_PENDING_DELETION |
  1143. JOB_BLOCKED_DEVQ | JOB_COMPOUND | JOB_COMPLETE)) {
  1144. continue;
  1145. }
  1146. // If we cant print to this printer, skip the job
  1147. if (!pIniPrinter ||
  1148. PrinterStatusBad(pIniPrinter->Status) ||
  1149. (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_WORK_OFFLINE) ||
  1150. (pIniPrinter->pIniSpooler->SpoolerFlags & SPL_OFFLINE)) {
  1151. continue;
  1152. }
  1153. // For direct printing dont consider spooling jobs
  1154. if (( (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_QUEUED) ||
  1155. (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_DIRECT) ) &&
  1156. (pIniJob->Status & JOB_SPOOLING)) {
  1157. continue;
  1158. }
  1159. // Check if the job can print immediately
  1160. CurrentTime = GetCurrentTimeInSeconds();
  1161. SecsToWait = GetTimeToWait(CurrentTime, pIniPrinter, pIniJob);
  1162. if (SecsToWait != 0) {
  1163. continue;
  1164. }
  1165. // Check if any port attached to this printer can print this job
  1166. for (dwIndex = 0;
  1167. dwIndex < pIniPrinter->cPorts;
  1168. ++dwIndex) {
  1169. pIniPort = pIniPrinter->ppIniPorts[dwIndex];
  1170. if (!pIniPort || (pIniPort->Status & PP_ERROR)) {
  1171. continue;
  1172. }
  1173. if (!(pIniPort->Status & PP_FILE) &&
  1174. (pIniJob->Status & JOB_PRINT_TO_FILE)) {
  1175. continue;
  1176. }
  1177. if ((pIniPort->Status & PP_FILE) &&
  1178. !(pIniJob->Status & JOB_PRINT_TO_FILE)) {
  1179. continue;
  1180. }
  1181. // Check if the port is already processing some job
  1182. if ( (pIniPort->pIniJob) &&
  1183. !(pIniPort->Status & PP_WAITING )){
  1184. continue;
  1185. }
  1186. // Check if the port has some chained jobs other than the current one
  1187. if ((pIniPort->Status & PP_THREADRUNNING) &&
  1188. (pIniPort->Status & PP_WAITING)) {
  1189. if ((pIniPort->pIniJob != NULL) &&
  1190. (pIniPort->pIniJob != pIniJob)) {
  1191. continue;
  1192. } else {
  1193. // We have found a port and a job to schedule
  1194. break;
  1195. }
  1196. }
  1197. }
  1198. if (dwIndex < pIniPrinter->cPorts) {
  1199. // We have a port and job
  1200. bReturn = TRUE;
  1201. pJobData->dwNumberOfTries += 1;
  1202. *ppIniJob = pIniJob;
  1203. *ppIniPort = pIniPort;
  1204. break;
  1205. }
  1206. }
  1207. return bReturn;
  1208. }