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.

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