This thread was useful in finding out the next run-time for a scheduled task.
How do I find out the next run time for a Scheduled Task?
But, is there also a way to simply get the next scheduled task due to run?
If I can get the date and name of the next task due to run, I can plug that date into a jQuery countdown timer, which will display a countdown to the next scheduled task, something like:
TaskABC due to run in:
12 03 20
hrs min sec
. This is for an admin interface in case you're wondering how geeky can people get:-)
EDIT
I had the same thought as Bill. But was curious if there was another way.
I poked around and apparently the internal Scheduler class maintains a list of upcoming tasks. The list is private, but you can use the same reflection technique to access it. Interestingly the list also includes system tasks like the mail spooler, session/application trackers, watchers, etecetera. So you must iterate through it until you find a "scheduled task" ie CronTabEntry
Below is a very lightly tested function that seems to do the trick in CF9. (Note, includes the CreateTimeStruct function from http://www.cflib.org).
Rules:
Returns a structure containing the name and time remaining until the next task. If no tasks were found, result.task is an empty string.
Excludes paused tasks
Usage:
result = new TaskUtil().getNextTask();
WriteDump(result);
CFC
component {
public struct function getNextTask() {
// get list of upcoming tasks from factory (UNDOCUMENTED)
local.scheduler = createObject("java", "coldfusion.server.ServiceFactory").getSchedulerService();
local.taskField = local.scheduler.getClass().getDeclaredField("_tasks");
local.taskField.setAccessible( true );
local.taskList = local.taskField.get(local.scheduler);
// taskList contains system jobs too, so we must iterate
// through the tasks to find the next "scheduled task"
local.nextTask = "";
local.tasks = local.taskList.iterator();
while ( local.tasks.hasNext() ) {
local.currTask = local.tasks.next();
local.className = local.currTask.getRunnable().getClass().name;
// exit as soon as we find a scheduled task that is NOT paused
if (local.className eq "coldfusion.scheduling.CronTabEntry"
&& !local.currTask.getRunnable().paused) {
local.nextTask = local.currTask;
break;
}
}
// if we found a task, calculate how many days, hours, etcetera
// until its next run time
local.details = { task="", remaining={} };
if ( isObject(local.nextTask) ) {
local.secondsToGo = (local.nextTask.getWhen() - now().getTime()) / 1000;
local.details.task = local.nextTask.getRunnable().task;
local.details.remaining = createTimeStruct(local.secondsToGo);
local.details.nextDate = dateAdd("s", local.nextTask.getWhen() / 1000
, "January 1 1970 00:00:00" );
}
return local.details;
}
/**
* Abbreviated version of CreateTimeStruct by Dave Pomerance
* See http://www.cflib.org/index.cfm?event=page.udfbyid&udfid=421
*
* #param timespan The timespan to convert.
* #return Returns a structure.
* #author Dave Pomerance
* #version 1, January 7, 2002
*/
public struct function CreateTimeStruct(required numeric timespan) {
var timestruct = StructNew();
var mask = "s";
// only 4 allowed values for mask - if not one of those, return blank struct
if (ListFind("d,h,m,s", mask)) {
// compute seconds
if (mask eq "s") {
timestruct.s = (timespan mod 60) + (timespan - Int(timespan));
timespan = int(timespan/60);
mask = "m";
} else timestruct.s = 0;
// compute minutes
if (mask eq "m") {
timestruct.m = timespan mod 60;
timespan = int(timespan/60);
mask = "h";
} else timestruct.m = 0;
// compute hours, days
if (mask eq "h") {
timestruct.h = timespan mod 24;
timestruct.d = int(timespan/24);
} else {
timestruct.h = 0;
timestruct.d = timespan;
}
}
return timestruct;
}
}
My first thought is to iterate Leigh's getNextRunTime(string taskName) function over the collection of tasks. You can get an array of structs containing the details of all scheduled tasks using taskArray = createobject("java","coldfusion.server.ServiceFactory").getCronService().listAll();
The key in the struct containing the task name is "task". So you can extract all the task names as an array for example, run Leigh's function on each element and determine which one will run next.
Related
I want to have a periodic timer loop (e.g. 1 second intervals). There are many ways to do that, but I haven't found a solution that would be suitable for unit testing.
Timer should be precise
Unit test should be able to skip the waiting
The closest that I came to a solution was to use coroutines: A simple loop with delay, runBlockingTest and advanceTimeBy.
coScope.launch {
while (isActive) {
// do stuff
delay(1000L)
}
}
and
#Test
fun timer_test() = coScope.runBlockingTest {
... // start job
advanceTimeBy(9_000L)
... // cancel job
}
It works to some degree, but the timer is not precise as it does not account for the execution time.
I haven't found a way to query internal timer used in a coroutine scope or a remaining timeout value inside withTimeoutOrNull:
coScope.launch {
withTimeoutOrNull(999_000_000L) { // max allowed looping time
while (isActive) {
// do stuff
val timeoutLeft // How to get that value ???
delay(timeoutLeft.mod(1000L))
}
}
}
Next idea was to use ticker:
coScope.launch {
val tickerChannel = ticker(1000L, 0L, coroutineContext)
var referenceTimer = 0L
for (event in tickerChannel) {
// do stuff
println(referenceTimer)
referenceTimer += 1000L
}
}
However, the connection between TestCoroutineDispatcher() and ticker does not produce right results:
private val coDispatcher = TestCoroutineDispatcher()
#Test timerTest() = runBlockingTest(coDispatcher) {
myTimer.lauchPeriodicJob()
advanceTimeBy(20_000L) // or delay(20_000L)
myTimer.cancelPeriodicJob()
println("end_of_test")
}
rather consistently results in:
0
1000
2000
3000
4000
5000
6000
end_of_test
I am also open for any alternative approaches that satisfy the two points above.
I have the code below. I would expect it to run daily at 17:00 if the config setting was not set otherwise the config setting will be used. So far no issue, the variable is set correctly. However: Instead of daily the job gets executed every minute and I cant figure out why. Is the scheduler not set up correctly?
TimeSpan timeOfExecution;
if (!TimeSpan.TryParse(ConfigurationManager.AppSettings["TimeOfExecution"], out timeOfExecution))
{
timeOfExecution = new TimeSpan(17, 0, 0);
}
var job = JobBuilder.Create<DailyReportJob>()
.WithIdentity("DailyReportJob")
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("DailyReportTrigger")
.WithDailyTimeIntervalSchedule(s => s.OnEveryDay().StartingDailyAt(new TimeOfDay(timeOfExecution.Hours, timeOfExecution.Minutes)))
.Build();
Scheduler.ScheduleJob(job, trigger);
Scheduler.ListenerManager.AddJobListener(AutofacJobListener);
Scheduler.Start();
The default time for this trigger is every minute, since you haven't specified otherwise.
You can check all the intervals using this code:
var dt = trigger.GetNextFireTimeUtc();
for (int i = 0; i < 10; i++)
{
if (dt == null)
break;
Console.WriteLine(dt.Value.ToLocalTime());
dt = trigger.GetFireTimeAfter(dt);
}
If you want to schedule your job to run once a day at 5pm, you can change your code adding a 24 hour interval:
var trigger = TriggerBuilder.Create()
.WithIdentity("DailyReportTrigger")
.WithDailyTimeIntervalSchedule(s => s.OnEveryDay().StartingDailyAt(new TimeOfDay(timeOfExecution.Hours, timeOfExecution.Minutes)))
.WithIntervalInHours(24)
.Build();
What is the most efficient way of reading in lots of images in CF / Railo and checking their width and height?
In my app, I need to typically read in about 20 images + and at the moment this takes up to 14 seconds to complete. A bit too long really.
theImageRead = ImageNew(theImageSrc);
if ( imageGetWidth(theImageRead) > 100 ) {
writeOutput('<img src="' & theImageSrc & '" />');
}
Images are read from a list of absolute URL's. I need to get images specified over a certain dimension.
If there's a quicker solution to this then I'd love to get your insight. Perhaps underlying java methods?
I am also using jSoup if there's anything in that which could help.
Thanks,
Michael.
I don't believe there's any way to determine the pixel dimensions of an image without reading the bytes and creating an image object. The main bottleneck here will be the http request overhead.
that said there are a few ways to speed up what you're trying to do.
use threads to concurrently request images, then when all threads have finished processing output the images.
If you display the same image or set of images more than once cache it. If you don't want to cache the actually image you can cache the metadata to avoid having to perform a http request for every image.
decide if you need to output all the images to the page immediately, or could some or all of these be deferred and loaded via and ajax request
I have written this utility function quite a while ago (it runs on older ColdFusion versions, too). Maybe it helps.
Note that this requires the Java Advanced Imaging Image I/O Tools (Jai-imageio). Download the .jar and put it in your class path (restarting CF is necessary).
/**
* Reads basic properties of many types of images. Values are
* returned as a struct consisting of the following elements:
*
* Property names, their types and default values:
* ImgInfo.width = 0 (pixels)
* ImgInfo.height = 0 (pixels)
* ImgInfo.size = 0 (bytes)
* ImgInfo.isGrayscale = false (boolean)
* ImgInfo.isFile = false (boolean)
* ImgInfo.success = false (boolean)
* ImgInfo.error = "" (string)
*
* #param FilePath Physical path to image file.
* #return A struct, as described.
*/
function GetImageProperties(FilePath) {
var ImgInfo = StructNew();
var jImageIO = CreateObject("java", "javax.imageio.ImageIO");
var jFile = CreateObject("java", "java.io.File").init(FilePath);
var jBufferedImage = 0;
var jColorSpace = 0;
ImgInfo.width = "";
ImgInfo.height = "";
ImgInfo.fileSize = 0;
ImgInfo.isGrayscale = false;
ImgInfo.isFile = jFile.isFile();
ImgInfo.success = false;
ImgInfo.error = "";
try {
jBufferedImage = jImageIO.read(jFile);
ImgInfo.fileSize = jFile.length();
ImgInfo.width = jBufferedImage.getWidth();
ImgInfo.height = jBufferedImage.getHeight();
jColorSpace = jBufferedImage.getColorModel().getColorSpace();
ImgInfo.isGrayscale = (jColorSpace.getType() eq jColorSpace.TYPE_GRAY);
ImgInfo.success = true;
}
catch (any ex) {
ImgInfo.error = ToString(ex);
}
jImageIO = JavaCast("null", "");
jFile = JavaCast("null", "");
jBufferedImage = JavaCast("null", "");
jColorSpace = JavaCast("null", "");
return ImgInfo;
}
Use like:
imageInfo = GetImageProperties(theImageSrc);
if (imageInfo.success and imageInfo.width > 100)
writeOutput('<img src="#HTMLEditFormat(theImageSrc)#" />');
}
In ColdFusion 9, is there a quick way to find out the next time that a scheduled task will attempt to run?
I would rather call a lower level API or such to have CF calculate it
in the same way it normally would. I've dumped the various services
and see no obvious methods to call that would help.
AFAIK, there is no one line solution. The main method CF uses to calculate the dates is CronTabEntry.NextRunTime. The CronTabEntry class represents the settings for a single task. The NextRunTime method(s) calculate the next potential run date, based on the task's settings. Handling of expired and paused tasks is done elsewhere, at runtime.
To duplicate the results you need to call NextRunTime and add a bit of logic to handle expired tasks. While NextRunTime method is private, it can still be accessed via reflection, with the help of Method.setAccessible(boolean).
I threw together the function below to demonstrate. The bulk of it is the reflection call (which is a bit more verbose in CF than its java equivalent). Anyway, it should return the same dates used by the CF scheduler.
Rules:
If the task end date/time has passed, returns an emtpy string ""
If it is a one-time task, that already executed, returns an empty string ""
For all other tasks (including paused tasks), returns the next scheduled date
Usage:
result = new TaskUtil().getNextRunTime("SomeTask");
WriteDump(result);
CFC
component {
public struct function getNextRunTime(required string scheduledTaskName) {
// load task settings from factory
local.cron = createobject("java","coldfusion.server.ServiceFactory").getCronService();
local.task = local.cron.findTask( arguments.scheduledTaskName );
// abort if we cannot find the task ..
if ( isNull(local.task) ) {
throw ("The specified task was not found: ["& arguments.scheduledTaskName &"]");
}
// Calculate the next POTENTIAL schedule date
local.isRecurring = listFindNoCase("daily,weekly,monthly", local.task.interval);
local.dateClass = createObject("java", "java.util.Date").getClass();
local.longClass = createObject("java", "java.lang.Long").TYPE;
local.stringClass = createObject("java", "java.lang.String").getClass();
// Construct the appropriate arguments
// Note: must load arguments / class types into arrays for java reflection
if (local.isRecurring) {
local.args = [ local.task.getStartDate(), local.task.getStartTime(), local.task.interval ];
local.types = [ local.dateClass, local.dateClass, local.stringClass ];
}
else {
local.args = [ local.task.getStartDate(), local.task.getStartTime(), local.task.getEndTime(), javacast("long", val(local.task.interval) * 1000) ];
local.types = [ local.dateClass, local.dateClass, local.dateClass, local.longClass ];
}
// Call CF's internal method to calculate the next date (UNDOCUMENTED)
local.internalMethod = local.task.getClass().getDeclaredMethod("NextRunTime", local.types );
local.internalMethod.setAccessible( true );
local.nextRunOnDate = local.internalMethod.invoke( local.task, local.args );
// Determine if the task will be rescheduled
local.isExpired = false;
if ( local.task.interval != "once" && structKeyExists( local.task, "end_date") ) {
// It is non-recurring, so determine if it already expired
local.expiresOnDate = local.task.mergeDates( local.task.getEndDate(), local.task.getEndTime() );
local.isExpired = dateCompare( local.nextRunOnDate, local.expiresOnDate, "s") >= 0;
}
else if ( local.task.interval == "once" && local.task.disabled) {
// It is a one time task that already executed
local.isExpired = true;
}
// construct and return results
local.result = {};
local.result.name = local.task.task;
local.result.isPaused = local.task.paused;
local.result.isExpired = local.isExpired;
local.result.nextRunTime = local.isExpired ? "" : local.nextRunOnDate;
return local.result;
}
}
Given that a certain business is open 8am-5pm, what is the best way to verify that a given timespan falls within that range? The date doesn't matter, just need to confirm that the entire requested time range falls within business hours.
Here is my sample data I have to begin with. (I'm using cfscript here simply to be concise)
<cfscript>
// array of given timeslots to test
timeslots = arrayNew(1);
// 1st appointment, entirely within normal business hours
appointment = structNew();
appointment.begin = "10:00 AM";
appointment.end = "2:00 PM";
arrayAppend(timeslots, appointment);
// 2nd appointment, outside of normal business hours
appointment = structNew();
appointment.begin = "7:00 PM";
appointment.end = "9:00 PM";
arrayAppend(timeslots, appointment);
// 3rd appointment, kind of tricky, partly within business hours, still should fail
appointment = structNew();
appointment.begin = "3:00 PM";
appointment.end = "6:00 PM";
arrayAppend(timeslots, appointment);
</cfscript>
Now, I'll loop over that array run a validation function on each struct. For the sake of example, it could look like this:
<cfoutput>
<cfloop array="#timeslots#" index="i">
<!--- Should return 'true' or 'false' for each item --->
#myValidator(i.begin, i.end)#<br />
</cfloop>
</cfoutput>
...So back to the real question,... how do I use Coldfusion to compare those times with the given business hours?
<cfscript>
// if input data is outside of the internal time range, return false, otherwise true
function myValidator(begin, end) {
var businessStart = "8:00 AM";
var businessEnd = "5:00 PM";
var result = false;
// what kind of tests do I put here to compare the times?
}
</cfscript>
UPDATE: my solution - rewrite of myValidator from above based on the feedback of Ben Doom and kevink.
// checks if any scheduled events fall outside business hours
function duringBusinessHours(startTime, endTime) {
var result = true;
var busStart = 800;
var busEnd = 1700;
var startNum = 0;
var endNum = 0;
// convert time string into a simple number:
// "7:00 AM" -> 700 | "3:00 PM" -> 1500
startNum = timeFormat(arguments.startTime, "Hmm");
endNum = timeFormat(arguments.endTime, "Hmm");
// start time must be smaller than end time
if (startNum GTE endNum) {
result = false;
// If start time is outside of business hours, fail
} else if (startNum LT busStart OR startNum GT busEnd) {
result = false;
// If end time is outside of business hours, fail
} else if (endNum LT busStart OR endNum GT busEnd) {
result = false;
}
return result;
}
If you don't care about days use a 24 hour clock, treat everything as a simple number and just use greater than, less than...
<cfif arguments.startime LT bizStart OR arguments.endtime GT bizEnd>
<cfreturn false>
<cfelse>
<cfreturn true>
</cfif>
Personally I would created two date objects using CreateDateTime with same m/d/y values and compared them, since CF can do this correctly.
You definitely want to create a date/time object with CreateDateTime and then compare dates/time frames that way. If you are taken input as a string from a webpage you can use ParseDateTime to convert a string representing a valid date/time format to a date/time object.