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.
1057 lines
34 KiB
1057 lines
34 KiB
//+----------------------------------------------------------------------------
|
|
//
|
|
// Job Object Handler
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1996.
|
|
//
|
|
// File: triggers.cxx
|
|
//
|
|
// Contents: trigger object and trigger collection object
|
|
//
|
|
// Classes: CTrigger and CBagOTriggers
|
|
//
|
|
// History: 27-June-95 EricB created
|
|
// 11-Nov-96 AnirudhS GetRunTimes: Changed return codes; various
|
|
// other fixes.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "..\pch\headers.hxx"
|
|
#pragma hdrstop
|
|
#include "job.hxx"
|
|
#include <StrSafe.h>
|
|
|
|
// prototypes for local functions:
|
|
HRESULT AddToList(FILETIME, FILETIME, CRun *, WORD *);
|
|
void AddDaysToFileTime(LPFILETIME pft, WORD Days);
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CTrigger::CTrigger
|
|
//
|
|
// Synopsis: Ctor
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
CTrigger::CTrigger(WORD iTrigger, CJob * pJob)
|
|
: m_iTrigger(iTrigger),
|
|
m_pJob(pJob),
|
|
m_cReferences(1)
|
|
{
|
|
schAssert(m_pJob != NULL);
|
|
m_pJob->AddRef();
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CTrigger::~CTrigger
|
|
//
|
|
// Synopsis: Dtor
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
CTrigger::~CTrigger(void)
|
|
{
|
|
m_pJob->Release();
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CTrigger::ITaskTrigger::GetTriggerString
|
|
//
|
|
// Synopsis: Returns a string representation of the trigger
|
|
//
|
|
// Arguments: [ppwszTrigger] - the place to return a string pointer
|
|
//
|
|
// Returns: HRESULTS
|
|
//
|
|
// Notes: The string is callee allocated and caller freed with
|
|
// CoTaskMemFree.
|
|
//-----------------------------------------------------------------------------
|
|
STDMETHODIMP
|
|
CTrigger::GetTriggerString(LPWSTR * ppwszTrigger)
|
|
{
|
|
//TRACE(CTrigger, GetTriggerString);
|
|
return(m_pJob->GetTriggerString(m_iTrigger, ppwszTrigger));
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CTrigger::ITaskTrigger::SetTrigger, public
|
|
//
|
|
// Synopsis: Sets the trigger values.
|
|
//
|
|
// Arguments: [pTrigger] - the struct containing the values
|
|
//
|
|
// Returns: HRESULTS
|
|
//
|
|
// Notes:
|
|
//-----------------------------------------------------------------------------
|
|
STDMETHODIMP
|
|
CTrigger::SetTrigger(const PTASK_TRIGGER pTrigger)
|
|
{
|
|
TRACE(CTrigger, SetTrigger);
|
|
|
|
if( NULL == pTrigger )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
//
|
|
// Check struct version.
|
|
//
|
|
|
|
if (pTrigger->cbTriggerSize != sizeof(TASK_TRIGGER))
|
|
{
|
|
//
|
|
// Don't attempt to modify triggers created by a later revision.
|
|
//
|
|
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
return(m_pJob->SetTrigger(m_iTrigger, pTrigger));
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Member: CTrigger::ITaskTrigger::GetTrigger
|
|
//
|
|
// Synopsis: Gets the trigger values.
|
|
//
|
|
// Arguments: [pTrigger] - pointer to caller supplied trigger structure
|
|
//
|
|
// Returns: HRESULTS
|
|
//
|
|
// Notes: pTrigger->cbTriggerSize must be set to sizeof(TASK_TRIGGER) on
|
|
// function entry. This provides for trigger struct versioning.
|
|
//-----------------------------------------------------------------------------
|
|
STDMETHODIMP
|
|
CTrigger::GetTrigger(PTASK_TRIGGER pTrigger)
|
|
{
|
|
//TRACE(CTrigger, GetTrigger);
|
|
if( NULL == pTrigger )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
return(m_pJob->GetTrigger(m_iTrigger, pTrigger));
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Functions: AddDaysToFileTime
|
|
//
|
|
// Synopsis: Convert the days value to filetime units and add it to
|
|
// the filetime.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
AddDaysToFileTime(LPFILETIME pft, WORD Days)
|
|
{
|
|
if (!Days)
|
|
{
|
|
return; // Nothing to do.
|
|
}
|
|
//
|
|
// ft = ft + Days * FILETIMES_PER_DAY;
|
|
//
|
|
ULARGE_INTEGER uli, uliSum;
|
|
uli.LowPart = pft->dwLowDateTime;
|
|
uli.HighPart = pft->dwHighDateTime;
|
|
uliSum.QuadPart = uli.QuadPart + (__int64)Days * FILETIMES_PER_DAY;
|
|
pft->dwLowDateTime = uliSum.LowPart;
|
|
pft->dwHighDateTime = uliSum.HighPart;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Functions: AddMinutesToFileTime
|
|
//
|
|
// Synopsis: Convert the minutes value to filetime units and add it to
|
|
// the filetime.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
AddMinutesToFileTime(LPFILETIME pft, DWORD Minutes)
|
|
{
|
|
if (!Minutes)
|
|
{
|
|
return; // Nothing to do.
|
|
}
|
|
//
|
|
// ft = ft + Minutes * FILETIMES_PER_MINUTE;
|
|
//
|
|
ULARGE_INTEGER uli, uliSum;
|
|
uli.LowPart = pft->dwLowDateTime;
|
|
uli.HighPart = pft->dwHighDateTime;
|
|
uliSum.QuadPart = uli.QuadPart + (__int64)Minutes * FILETIMES_PER_MINUTE;
|
|
pft->dwLowDateTime = uliSum.LowPart;
|
|
pft->dwHighDateTime = uliSum.HighPart;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GetTriggerRunTimes
|
|
//
|
|
// Synopsis: Computes a set of run times for this trigger that fall between
|
|
// the bracketing times -- pstBracketBegin is inclusive,
|
|
// pstBracketEnd is exclusive -- and merges them with the list of
|
|
// run times passed in.
|
|
//
|
|
// Arguments: [jt] - Inspected trigger.
|
|
// [pstBracketBegin] - the start of the bracketing period
|
|
// [pstBracketEnd] - the end of the bracketing period, may
|
|
// be NULL
|
|
// [pCount] - on both entry and exit, points to the
|
|
// number of CRun elements in the list.
|
|
// CODEWORK: Make this a private member of
|
|
// CRunList.
|
|
// [cLimit] - the maximum number of elements that the
|
|
// list may grow to.
|
|
// [pRunList] - the list of run time objects, can
|
|
// be NULL if just checking to see if there
|
|
// will be *any* runs. (Note: If it's NULL,
|
|
// duplicate run times are not detected, so
|
|
// pCount may be overestimated on return.)
|
|
// [ptszJobName],
|
|
// [dwJobFlags],
|
|
// [dwMaxRunTime] - the last 3 params are used for the CRun
|
|
// objects as their member data.
|
|
// [wIdleWait] - the job's idle wait period
|
|
// [wIdleDeadline] - time to wait for idle wait period
|
|
//
|
|
// Returns: S_OK: The trigger is a time-based trigger and is enabled,
|
|
// and zero or more of its run times have been added to the
|
|
// list (subject to cLimit and the bracketing period); or,
|
|
// the trigger is an event trigger but will expire before
|
|
// the bracketing period.
|
|
// SCHED_S_EVENT_TRIGGER: this is an event trigger that will be
|
|
// active (not expired) during the bracketing period.
|
|
// SCHED_S_TASK_NO_VALID_TRIGGERS: the trigger is disabled or
|
|
// not set.
|
|
// Failure HRESULTs: Other failures.
|
|
//
|
|
// Notes: The trigger time list is callee allocated and caller freed. The
|
|
// caller must use delete to free this list.
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT
|
|
GetTriggerRunTimes(
|
|
TASK_TRIGGER & jt,
|
|
const SYSTEMTIME * pstBracketBegin,
|
|
const SYSTEMTIME * pstBracketEnd,
|
|
WORD * pCount,
|
|
WORD cLimit,
|
|
CTimeRunList * pRunList,
|
|
LPTSTR ptszJobName,
|
|
DWORD dwJobFlags,
|
|
DWORD dwMaxRunTime,
|
|
WORD wIdleWait,
|
|
WORD wIdleDeadline)
|
|
{
|
|
TRACE_FUNCTION3(GetRunTimes);
|
|
DWORD dwRet;
|
|
|
|
schAssert(cLimit > 0); // If cLimit is 0, it's not clear what to return
|
|
schAssert(cLimit <= TASK_MAX_RUN_TIMES);
|
|
schAssert(*pCount <= cLimit);
|
|
|
|
//
|
|
// Return if this trigger hasn't been set or if it is disabled.
|
|
//
|
|
if (jt.rgFlags & JOB_TRIGGER_I_FLAG_NOT_SET ||
|
|
jt.rgFlags & TASK_TRIGGER_FLAG_DISABLED)
|
|
{
|
|
return SCHED_S_TASK_NO_VALID_TRIGGERS;
|
|
}
|
|
|
|
//
|
|
// Event triggers don't have set run times.
|
|
//
|
|
switch (jt.TriggerType)
|
|
{
|
|
case TASK_EVENT_TRIGGER_ON_IDLE:
|
|
case TASK_EVENT_TRIGGER_AT_SYSTEMSTART:
|
|
case TASK_EVENT_TRIGGER_AT_LOGON:
|
|
// Not yet implemented:
|
|
// case TASK_EVENT_TRIGGER_ON_APM_RESUME:
|
|
|
|
//
|
|
// Check if the trigger expires before the beginning of the bracket
|
|
//
|
|
if (jt.rgFlags & TASK_TRIGGER_FLAG_HAS_END_DATE)
|
|
{
|
|
SYSTEMTIME stEnd;
|
|
stEnd.wYear = jt.wEndYear;
|
|
stEnd.wMonth = jt.wEndMonth;
|
|
stEnd.wDay = jt.wEndDay;
|
|
// IsFirstDateEarlier ignores other fields
|
|
|
|
if (IsFirstDateEarlier(&stEnd, pstBracketBegin))
|
|
{
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
return SCHED_S_EVENT_TRIGGER;
|
|
}
|
|
|
|
SYSTEMTIME st = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
//
|
|
// Convert to FILETIMEs and check if the trigger lifetime intersects the
|
|
// requested run bracket.
|
|
// If there is a trigger end date, then one of three conditions holds:
|
|
// a. *pstBracketBegin > jt.End{Month/Day/Year}
|
|
// result, no runs
|
|
// b. *pstBracketBegin < jt.End{Month/Day/Year} < *pstBracketEnd
|
|
// result, return all runs between *pstBracketBegin and
|
|
// jt.End{Month/Day/Year}
|
|
// c. jt.End{Month/Day/Year} > *pstBracketEnd
|
|
// result, return all runs between *pstBracketBegin and *pstBracketEnd
|
|
// In addition, if there is a bracket end we check:
|
|
// d. *pstBracketEnd <= jt.Begin{Month/Day/Year}
|
|
// result, no runs
|
|
//
|
|
FILETIME ftTriggerBegin, ftTriggerEnd, ftBracketBegin, ftBracketEnd;
|
|
|
|
if (!SystemTimeToFileTime(pstBracketBegin, &ftBracketBegin))
|
|
{
|
|
dwRet = GetLastError();
|
|
ERR_OUT("GetRunTimes, convert pstBracketBegin", dwRet);
|
|
return HRESULT_FROM_WIN32(dwRet);
|
|
}
|
|
|
|
st.wYear = jt.wBeginYear;
|
|
st.wMonth = jt.wBeginMonth;
|
|
st.wDay = jt.wBeginDay;
|
|
st.wHour = jt.wStartHour;
|
|
st.wMinute = jt.wStartMinute;
|
|
|
|
if (!SystemTimeToFileTime(&st, &ftTriggerBegin))
|
|
{
|
|
dwRet = GetLastError();
|
|
ERR_OUT("GetRunTimes, convert TriggerBegin", dwRet);
|
|
return HRESULT_FROM_WIN32(dwRet);
|
|
}
|
|
|
|
st.wHour = 23; // set to the last hour of the day.
|
|
st.wMinute = 59; // set to the last minute of the day.
|
|
st.wSecond = 59; // set to the last second of the day.
|
|
st.wMilliseconds = 0;
|
|
|
|
if (jt.rgFlags & TASK_TRIGGER_FLAG_HAS_END_DATE)
|
|
{
|
|
st.wYear = jt.wEndYear;
|
|
st.wMonth = jt.wEndMonth;
|
|
st.wDay = jt.wEndDay;
|
|
|
|
if (!SystemTimeToFileTime(&st, &ftTriggerEnd))
|
|
{
|
|
dwRet = GetLastError();
|
|
ERR_OUT("GetRunTimes, convert TriggerEnd", dwRet);
|
|
return HRESULT_FROM_WIN32(dwRet);
|
|
}
|
|
|
|
if (CompareFileTime(&ftTriggerEnd, &ftBracketBegin) < 0)
|
|
{
|
|
//
|
|
// Trigger end time is before the run bracket begin time (case a.).
|
|
//
|
|
return S_OK;
|
|
}
|
|
}
|
|
else // no trigger end date.
|
|
{
|
|
//
|
|
// Create an end date that is reasonably large.
|
|
// BUGBUG Change this to MAX_FILETIME - but should be tested.
|
|
//
|
|
st.wMonth = 12;
|
|
st.wDay = 31;
|
|
st.wYear = 2200;
|
|
|
|
if (!SystemTimeToFileTime(&st, &ftTriggerEnd))
|
|
{
|
|
dwRet = GetLastError();
|
|
ERR_OUT("GetRunTimes, convert TriggerEnd", dwRet);
|
|
return HRESULT_FROM_WIN32(dwRet);
|
|
}
|
|
}
|
|
|
|
if (pstBracketEnd)
|
|
{
|
|
if (!SystemTimeToFileTime(pstBracketEnd, &ftBracketEnd))
|
|
{
|
|
dwRet = GetLastError();
|
|
ERR_OUT("GetRunTimes, convert pstBracketEnd", dwRet);
|
|
return HRESULT_FROM_WIN32(dwRet);
|
|
}
|
|
|
|
if (CompareFileTime(&ftTriggerBegin, &ftBracketEnd) >= 0)
|
|
{
|
|
//
|
|
// The trigger start date is after the bracket end date, there are
|
|
// no runs (case d.).
|
|
//
|
|
return S_OK;
|
|
}
|
|
|
|
if (CompareFileTime(&ftTriggerEnd, &ftBracketEnd) < 0)
|
|
{
|
|
//
|
|
// Trigger end is before bracket end, so set bracket end to
|
|
// trigger end (case b.).
|
|
//
|
|
ftBracketEnd = ftTriggerEnd;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No bracket end, so use trigger end (case c.).
|
|
//
|
|
ftBracketEnd = ftTriggerEnd;
|
|
}
|
|
|
|
FILETIME ftRun, ftDurationStart, ftDurationEnd;
|
|
WORD rgfRunDOW[JOB_DAYS_PER_WEEK], i;
|
|
WORD rgfDaysOfMonth[JOB_DAYS_PER_MONTHMAX];
|
|
WORD rgfMonths[JOB_MONTHS_PER_YEAR];
|
|
WORD wDay, wBeginDOW, wCurDOW, wCurDay, wLastDOM, wCurMonth, wCurYear;
|
|
WORD cRunDOW, iRunDOW, IndexStart;
|
|
BOOL fWrapped;
|
|
fWrapped = FALSE;
|
|
|
|
//
|
|
// Calculate the trigger's first run time.
|
|
//
|
|
switch (jt.TriggerType)
|
|
{
|
|
case TASK_TIME_TRIGGER_ONCE:
|
|
// fall through to daily:
|
|
|
|
case TASK_TIME_TRIGGER_DAILY:
|
|
//
|
|
// The first run time is the trigger begin time.
|
|
//
|
|
ftRun = ftTriggerBegin;
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_WEEKLY:
|
|
//
|
|
// At jobs clear the DOW bits, so make sure we don't have an expired
|
|
// At job.
|
|
//
|
|
if (jt.Type.Weekly.rgfDaysOfTheWeek == 0)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// See what day of the week the trigger begin day is. SYSTEMTIME
|
|
// defines Sunday = 0, Monday = 1, etc.
|
|
//
|
|
FileTimeToSystemTime(&ftTriggerBegin, &st);
|
|
wBeginDOW = st.wDayOfWeek;
|
|
//
|
|
// Convert the trigger data run day bit array into a boolean array
|
|
// so that the results can be compared with the SYSTEMTIME value.
|
|
// This array will also be used in the main loop.
|
|
//
|
|
for (i = 0; i < JOB_DAYS_PER_WEEK; i++)
|
|
{
|
|
rgfRunDOW[i] = (jt.Type.Weekly.rgfDaysOfTheWeek >> i) & 0x1;
|
|
}
|
|
//
|
|
// Find the first set day-of-the-week after the trigger begin day.
|
|
//
|
|
for (i = 0; i < JOB_DAYS_PER_WEEK; i++)
|
|
{
|
|
wCurDOW = wBeginDOW + i;
|
|
if (wCurDOW >= JOB_DAYS_PER_WEEK)
|
|
{
|
|
wCurDOW -= JOB_DAYS_PER_WEEK;
|
|
}
|
|
if (rgfRunDOW[wCurDOW])
|
|
{
|
|
ftRun = ftTriggerBegin;
|
|
AddDaysToFileTime(&ftRun, i);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_MONTHLYDATE:
|
|
//
|
|
// At jobs clear the days bits, so make sure we don't have an expired
|
|
// At job.
|
|
//
|
|
if (jt.Type.MonthlyDate.rgfDays == 0)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// Convert the bit fields to boolean arrays.
|
|
// These arrays will also be used in the main loop.
|
|
//
|
|
for (i = 0; i < JOB_DAYS_PER_MONTHMAX; i++)
|
|
{
|
|
rgfDaysOfMonth[i] = (WORD)(jt.Type.MonthlyDate.rgfDays >> i) & 0x1;
|
|
}
|
|
for (i = 0; i < JOB_MONTHS_PER_YEAR; i++)
|
|
{
|
|
rgfMonths[i] = (jt.Type.MonthlyDate.rgfMonths >> i) & 0x1;
|
|
}
|
|
|
|
wCurDay = jt.wBeginDay;
|
|
wCurMonth = jt.wBeginMonth;
|
|
wCurYear = jt.wBeginYear;
|
|
BOOL fDayOverflow, fDayFound;
|
|
fDayFound = FALSE;
|
|
do
|
|
{
|
|
MonthDays(wCurMonth, wCurYear, &wLastDOM);
|
|
//
|
|
// Find the first run day after the trigger start day, including
|
|
// the trigger start day.
|
|
//
|
|
for (i = 0; i < wLastDOM; i++)
|
|
{
|
|
if (wCurDay > wLastDOM)
|
|
{
|
|
//
|
|
// Adjust for wrapping.
|
|
//
|
|
wCurDay = 1;
|
|
fWrapped = TRUE;
|
|
}
|
|
if (rgfDaysOfMonth[wCurDay - 1])
|
|
{
|
|
fDayFound = TRUE;
|
|
break;
|
|
}
|
|
wCurDay++;
|
|
}
|
|
//
|
|
// Find the first run month.
|
|
//
|
|
for (i = 0; i < JOB_MONTHS_PER_YEAR; i++)
|
|
{
|
|
if (wCurMonth > JOB_MONTHS_PER_YEAR)
|
|
{
|
|
wCurMonth = 1;
|
|
wCurYear++;
|
|
}
|
|
//
|
|
// Check for run month match. Note that rgfMonths is zero based
|
|
// and wCurMonth is one based.
|
|
//
|
|
if (rgfMonths[wCurMonth - 1])
|
|
{
|
|
if (fWrapped && !i)
|
|
{
|
|
//
|
|
// Even though we have a match for run month, the run
|
|
// date for the first month has passed, so move on to
|
|
// the next run month.
|
|
//
|
|
fWrapped = FALSE;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
wCurMonth++;
|
|
}
|
|
//
|
|
// Check for days overflow.
|
|
//
|
|
MonthDays(wCurMonth, wCurYear, &wLastDOM);
|
|
if (wCurDay > wLastDOM)
|
|
{
|
|
//
|
|
// Note that this clause would be entered infinitely if there
|
|
// were no valid dates. ITask::SetTrigger validates the data to
|
|
// ensure that there are valid dates.
|
|
//
|
|
fDayOverflow = TRUE;
|
|
fDayFound = FALSE;
|
|
wCurDay = 1;
|
|
wCurMonth++;
|
|
if (wCurMonth > JOB_MONTHS_PER_YEAR)
|
|
{
|
|
wCurMonth = 1;
|
|
wCurYear++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fDayOverflow = FALSE;
|
|
}
|
|
} while (fDayOverflow & !fDayFound);
|
|
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_MONTHLYDOW:
|
|
//
|
|
// Convert the bit fields to boolean arrays.
|
|
// These arrays will also be used in the main loop.
|
|
//
|
|
cRunDOW = 0;
|
|
for (i = 0; i < JOB_DAYS_PER_WEEK; i++)
|
|
{
|
|
if ((jt.Type.MonthlyDOW.rgfDaysOfTheWeek >> i) & 0x1)
|
|
{
|
|
cRunDOW++;
|
|
rgfRunDOW[i] = TRUE;
|
|
}
|
|
else
|
|
{
|
|
rgfRunDOW[i] = FALSE;
|
|
}
|
|
}
|
|
for (i = 0; i < JOB_MONTHS_PER_YEAR; i++)
|
|
{
|
|
rgfMonths[i] = (jt.Type.MonthlyDOW.rgfMonths >> i) & 0x1;
|
|
}
|
|
//
|
|
// See if the trigger start month is in rgfMonths and if not
|
|
// move to the first month in rgfMonths after jt.BeginMonth.
|
|
//
|
|
wCurMonth = jt.wBeginMonth;
|
|
wCurYear = jt.wBeginYear;
|
|
BOOL fInStartMonth;
|
|
IndexStart = 0;
|
|
CheckNextMonth:
|
|
for (i = IndexStart; i < (JOB_MONTHS_PER_YEAR + IndexStart); i++)
|
|
{
|
|
//
|
|
// Check for run month match. Note that rgfMonths is zero based
|
|
// and wCurMonth is one based.
|
|
//
|
|
if (rgfMonths[wCurMonth - 1])
|
|
{
|
|
break;
|
|
}
|
|
|
|
wCurMonth++;
|
|
if (wCurMonth > JOB_MONTHS_PER_YEAR)
|
|
{
|
|
wCurMonth -= JOB_MONTHS_PER_YEAR;
|
|
wCurYear++;
|
|
}
|
|
}
|
|
|
|
fInStartMonth = i == 0;
|
|
|
|
//
|
|
// See what day of the week the first day of the month is.
|
|
//
|
|
st.wMonth = wCurMonth;
|
|
st.wDay = 1;
|
|
st.wYear = wCurYear;
|
|
|
|
//
|
|
// Convert to FILETIME and back to SYSTEMTIME to get wDayOfWeek.
|
|
//
|
|
SystemTimeToFileTime(&st, &ftRun);
|
|
FileTimeToSystemTime(&ftRun, &st);
|
|
wBeginDOW = st.wDayOfWeek;
|
|
|
|
//
|
|
// Find the first run DayOftheWeek. If it is before the start
|
|
// day, find the next and so on until after the start day.
|
|
//
|
|
iRunDOW = cRunDOW;
|
|
|
|
for (i = 0; i < JOB_DAYS_PER_WEEK; i++)
|
|
{
|
|
wCurDOW = wBeginDOW + i;
|
|
wCurDay = 1 + i;
|
|
|
|
if (wCurDOW >= JOB_DAYS_PER_WEEK)
|
|
{
|
|
wCurDOW -= JOB_DAYS_PER_WEEK;
|
|
}
|
|
|
|
if (rgfRunDOW[wCurDOW])
|
|
{
|
|
iRunDOW--;
|
|
wCurDay += (jt.Type.MonthlyDOW.wWhichWeek - 1)
|
|
* JOB_DAYS_PER_WEEK;
|
|
|
|
MonthDays(wCurMonth, wCurYear, &wLastDOM);
|
|
|
|
if (wCurDay > wLastDOM)
|
|
{
|
|
//
|
|
// This case can be reached if
|
|
// jt.Type.MonthlyDOW.wWhichWeek == TASK_LAST_WEEK
|
|
// which means to always run on the last occurrence of
|
|
// this day for the month.
|
|
//
|
|
wCurDay -= JOB_DAYS_PER_WEEK;
|
|
}
|
|
|
|
if (fInStartMonth && wCurDay < jt.wBeginDay)
|
|
{
|
|
if (iRunDOW)
|
|
{
|
|
//
|
|
// There are more runs this month, so check those.
|
|
//
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Start with the next run month.
|
|
//
|
|
IndexStart++;
|
|
goto CheckNextMonth;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
wDay = 1 + i;
|
|
break;
|
|
|
|
default:
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (jt.TriggerType == TASK_TIME_TRIGGER_MONTHLYDATE ||
|
|
jt.TriggerType == TASK_TIME_TRIGGER_MONTHLYDOW)
|
|
{
|
|
st.wYear = wCurYear;
|
|
st.wMonth = wCurMonth;
|
|
st.wDay = wCurDay;
|
|
st.wHour = jt.wStartHour;
|
|
st.wMinute = jt.wStartMinute;
|
|
st.wSecond = st.wMilliseconds = 0;
|
|
SystemTimeToFileTime(&st, &ftRun);
|
|
}
|
|
|
|
//
|
|
// Set the initial duration period endpoints.
|
|
//
|
|
// ftDurationEnd = ftDurationStart + jt.MinutesDuration
|
|
// * FILETIMES_PER_MINUTE;
|
|
//
|
|
ftDurationStart = ftDurationEnd = ftRun;
|
|
AddMinutesToFileTime(&ftDurationEnd, jt.MinutesDuration);
|
|
BOOL fPassedDurationEnd = FALSE;
|
|
|
|
//
|
|
// Main loop. Find all of the runs after the initial run.
|
|
// Stop when the run goes past the bracket end.
|
|
//
|
|
while (CompareFileTime(&ftRun, &ftBracketEnd) < 0)
|
|
{
|
|
//
|
|
// If the run falls within the run bracket, add it to the list.
|
|
//
|
|
if (CompareFileTime(&ftRun, &ftBracketBegin) >= 0)
|
|
{
|
|
if (pRunList != NULL)
|
|
{
|
|
FILETIME ftKillTime = MAX_FILETIME;
|
|
if (jt.rgFlags & TASK_TRIGGER_FLAG_KILL_AT_DURATION_END)
|
|
{
|
|
ftKillTime = ftDurationEnd;
|
|
}
|
|
|
|
FILETIME ftDeadline = ftTriggerEnd;
|
|
if (dwJobFlags & TASK_FLAG_START_ONLY_IF_IDLE)
|
|
{
|
|
FILETIME ftIdleDeadline = ftRun;
|
|
|
|
AddMinutesToFileTime(&ftIdleDeadline, wIdleDeadline);
|
|
ftDeadline = minFileTime(ftTriggerEnd, ftIdleDeadline);
|
|
}
|
|
|
|
HRESULT hr = pRunList->AddSorted(ftRun, ftDeadline, ftKillTime,
|
|
ptszJobName, dwJobFlags, dwMaxRunTime,
|
|
wIdleWait, pCount, cLimit);
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
schAssert(*pCount <= cLimit);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
//
|
|
// The run time is later than the last element in the list
|
|
// and the list has reached its size limit. So don't
|
|
// bother computing any more run times.
|
|
//
|
|
return S_OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (*pCount < cLimit)
|
|
{
|
|
(*pCount)++;
|
|
}
|
|
|
|
if (*pCount == cLimit)
|
|
{
|
|
//
|
|
// Computing more run times will have no effect.
|
|
//
|
|
return S_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Calculate the next run time.
|
|
//
|
|
|
|
//
|
|
// If there is minutes repetition (MinutesInterval non-zero), then
|
|
// compute all of the runs in the duration period.
|
|
//
|
|
|
|
if (jt.MinutesInterval)
|
|
{
|
|
//
|
|
// Add the minutes interval.
|
|
//
|
|
AddMinutesToFileTime(&ftRun, jt.MinutesInterval);
|
|
|
|
//
|
|
// See if we are at the end of this duration period.
|
|
//
|
|
if (CompareFileTime(&ftDurationEnd, &ftRun) <= 0)
|
|
{
|
|
fPassedDurationEnd = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there is no minutes repetition (MinutesInterval is zero) or we
|
|
// have passed the end of the duration period, then calculate the next
|
|
// duration start (which is also the next run).
|
|
//
|
|
if (!jt.MinutesInterval || fPassedDurationEnd)
|
|
{
|
|
switch (jt.TriggerType)
|
|
{
|
|
case TASK_TIME_TRIGGER_ONCE:
|
|
return S_OK;
|
|
|
|
case TASK_TIME_TRIGGER_DAILY:
|
|
//
|
|
// ftNextRun = ftCurRun + DaysInterval * FILETIMES_PER_DAY;
|
|
//
|
|
AddDaysToFileTime(&ftDurationStart, jt.Type.Daily.DaysInterval);
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_WEEKLY:
|
|
fWrapped = FALSE;
|
|
//
|
|
// Find the next DayOfWeek to run on.
|
|
//
|
|
for (i = 1; i <= JOB_DAYS_PER_WEEK; i++)
|
|
{
|
|
wCurDOW++;
|
|
if (wCurDOW >= JOB_DAYS_PER_WEEK)
|
|
{
|
|
//
|
|
// We have wrapped into the next week.
|
|
//
|
|
wCurDOW -= JOB_DAYS_PER_WEEK;
|
|
fWrapped = TRUE;
|
|
}
|
|
if (rgfRunDOW[wCurDOW])
|
|
{
|
|
AddDaysToFileTime(&ftDurationStart, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fWrapped)
|
|
{
|
|
//
|
|
// Starting a new week, so add the weeks increment.
|
|
//
|
|
AddDaysToFileTime(&ftDurationStart,
|
|
(jt.Type.Weekly.WeeksInterval - 1)
|
|
* JOB_DAYS_PER_WEEK);
|
|
}
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_MONTHLYDATE:
|
|
BOOL fDayFound;
|
|
fWrapped = FALSE;
|
|
fDayFound = FALSE;
|
|
//
|
|
// Find the next day to run.
|
|
//
|
|
do
|
|
{
|
|
MonthDays(wCurMonth, wCurYear, &wLastDOM);
|
|
for (i = 1; i <= wLastDOM; i++)
|
|
{
|
|
wCurDay++;
|
|
if (wCurDay > wLastDOM)
|
|
{
|
|
//
|
|
// Adjust for wrapping.
|
|
//
|
|
wCurDay = 1;
|
|
fWrapped = TRUE;
|
|
wCurMonth++;
|
|
if (wCurMonth > JOB_MONTHS_PER_YEAR)
|
|
{
|
|
wCurMonth = 1;
|
|
wCurYear++;
|
|
}
|
|
MonthDays(wCurMonth, wCurYear, &wLastDOM);
|
|
}
|
|
if (rgfDaysOfMonth[wCurDay - 1])
|
|
{
|
|
fDayFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (fWrapped || !fDayFound)
|
|
{
|
|
//
|
|
// The prior month is done, find the next month.
|
|
//
|
|
for (i = 1; i <= JOB_MONTHS_PER_YEAR; i++)
|
|
{
|
|
if (wCurMonth > JOB_MONTHS_PER_YEAR)
|
|
{
|
|
wCurMonth = 1;
|
|
wCurYear++;
|
|
}
|
|
if (rgfMonths[wCurMonth - 1])
|
|
{
|
|
fWrapped = FALSE;
|
|
break;
|
|
}
|
|
wCurMonth++;
|
|
}
|
|
}
|
|
} while (!fDayFound);
|
|
break;
|
|
|
|
case TASK_TIME_TRIGGER_MONTHLYDOW:
|
|
if (!iRunDOW)
|
|
{
|
|
//
|
|
// All of the runs for the current month are done, find the
|
|
// next month.
|
|
//
|
|
for (i = 0; i < JOB_MONTHS_PER_YEAR; i++)
|
|
{
|
|
wCurMonth++;
|
|
if (wCurMonth > JOB_MONTHS_PER_YEAR)
|
|
{
|
|
wCurMonth = 1;
|
|
wCurYear++;
|
|
}
|
|
if (rgfMonths[wCurMonth - 1])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
// See what day of the week the first day of the month is.
|
|
//
|
|
st.wMonth = wCurMonth;
|
|
st.wDay = wDay = 1;
|
|
st.wYear = wCurYear;
|
|
SystemTimeToFileTime(&st, &ftRun);
|
|
FileTimeToSystemTime(&ftRun, &st);
|
|
wCurDOW = st.wDayOfWeek;
|
|
iRunDOW = cRunDOW;
|
|
//
|
|
// Start at the first run DOW for this next month.
|
|
//
|
|
IndexStart = 0;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Start at the next run DOW for the current month.
|
|
//
|
|
IndexStart = 1;
|
|
}
|
|
|
|
//
|
|
// Find the next DayOfWeek to run on.
|
|
//
|
|
for (i = IndexStart; i <= JOB_DAYS_PER_WEEK; i++)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
wCurDOW++;
|
|
wDay++;
|
|
}
|
|
if (wCurDOW >= JOB_DAYS_PER_WEEK)
|
|
{
|
|
wCurDOW -= JOB_DAYS_PER_WEEK;
|
|
}
|
|
if (rgfRunDOW[wCurDOW])
|
|
{
|
|
//
|
|
// Found a run DayOfWeek.
|
|
//
|
|
iRunDOW--;
|
|
wCurDay = wDay + (jt.Type.MonthlyDOW.wWhichWeek - 1)
|
|
* JOB_DAYS_PER_WEEK;
|
|
WORD wLastDOM;
|
|
MonthDays(wCurMonth, wCurYear, &wLastDOM);
|
|
if (wCurDay > wLastDOM)
|
|
{
|
|
//
|
|
// This case can be reached if
|
|
// jt.Type.MonthlyDOW.wWhichWeek == JOB_LAST_WEEK
|
|
// which means to always run on the last occurance
|
|
// of this day for the month.
|
|
//
|
|
wCurDay -= JOB_DAYS_PER_WEEK;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (jt.TriggerType == TASK_TIME_TRIGGER_MONTHLYDATE ||
|
|
jt.TriggerType == TASK_TIME_TRIGGER_MONTHLYDOW)
|
|
{
|
|
st.wYear = wCurYear;
|
|
st.wMonth = wCurMonth;
|
|
st.wDay = wCurDay;
|
|
st.wHour = jt.wStartHour;
|
|
st.wMinute = jt.wStartMinute;
|
|
st.wSecond = st.wMilliseconds = 0;
|
|
SystemTimeToFileTime(&st, &ftDurationStart);
|
|
}
|
|
|
|
//
|
|
// Calc the next duration period endpoints.
|
|
//
|
|
ftRun = ftDurationEnd = ftDurationStart;
|
|
|
|
AddMinutesToFileTime(&ftDurationEnd, jt.MinutesDuration);
|
|
|
|
fPassedDurationEnd = FALSE;
|
|
}
|
|
|
|
} // while
|
|
|
|
return S_OK;
|
|
}
|
|
|