This page uses CSS, and renders properly in the latest versions of Microsoft Internet Explorer and Mozilla.
This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.
TOP UP: Applied Frontier NEXT: TCP/IP

37

Scheduler

The Scheduler is a suite that triggers the performance of tasks associated with a certain time, or with the passage of a certain time interval since the previous performance. You might want to perform a backup every night in the middle of the night, or put up a dialog every couple of hours telling you to take a break, or put up a dialog once telling you that your dentist appointment is in an hour.1 True, a recurring task could also be implemented simply as an agent; but the Scheduler brings all such tasks together under one roof, as it were, and avoids the problem of all agents being triggered when Frontier first starts up.

Every task waiting to be performed is represented by a table in user.scheduler.tasks (a "task table"), consisting of four entries:

error

Temporary storage for an error message in case a runtime error arises during execution of the task. Its initial value is the empty string.

taskTime

The date-time when the task should next be performed. It is pointless to specify a time more precise than to the minute, and even then there is a possibility that the task won't be performed until other tasks have cleared the queue.

minutesBetweenRuns

The number of minutes after taskTime when the task should be performed again. When the Scheduler performs a task, it automatically increases its taskTime by adding minutesBetweenRuns to it repeatedly until taskTime is after the current time (this is called "rescheduling"). If a task is to be performed only once, minutesBetweenRuns should be 0; when the Scheduler performs such a task, it deletes its task table.

script

A string, which will be evaluated when taskTime comes; this is the action of the task. script will probably represent a verb call, so performing the task will consist of calling that verb. Tasks are performed with evaluate(), not thread.evaluate(); this is why a task may have to wait past its taskTime until other tasks have finished performing. (Of course, the task itself could involve spawning a thread; that's up to you.)

The heart of the suite is an agent, system.agents.["Scheduler Monitor"], which calls scheduler.monitor() to do all the work. scheduler.monitor() first checks the current time; then, for every task whose taskTime is at or before that moment, it reschedules the task and performs it; finally, it calls clock.sleepFor() in such a way that the agent will run again at the start of the next minute.

While a task is running, scheduler.currentTask contains the address of its task table. The task, as it runs, can use this information if desired; it might be used to maintain a global between calls, or a task could change its own taskTime or minutesBetweenRuns - even its own script.

Suppose, for example, you have a PTA meeting on the third Tuesday of every month, so you want a dialog to appear each month on that day. That's a complicated concept, but the Scheduler can handle it because the task can reschedule itself. The task table might be set up with an initial taskTime of clock.now()+60 and a minutesBetweenRuns value of 60; these values aren't very important because the first performance of the task will change them (without putting up the dialog). This works because scheduler.monitor() reschedules a task before running it; so the Scheduler reschedules our task, but our task then reschedules itself and has the final say.

If script is "pta()", then people.[user.initials].pta() might look like:

on getThirdTue(mo, yr)
    local(aDate, aDay = 1)
    aDate = date.set(aDay, mo, yr, 1, 0, 0)
    while date.dayOfWeek(aDate) != 3
        aDate = date.set(++aDay, mo, yr, 1, 0, 0)
    aDate = date.set(aDay+14, mo, yr, 1, 0, 0)
    return aDate
local (theTask = scheduler.currentTask)
local (day, month, year, hour, min, sec, aDate, today)
date.get(clock.now(), @day, @month, @year, @hour, @min, @sec)
today = date.set(day, month, year, 1, 0, 0)
aDate = getThirdTue(month, year)
if today < aDate « reschedule for this month
    theTask^.taskTime = aDate
if today == aDate
    dialog.alert ("You have a PTA meeting today.")
    today = today + 1 « fall thru to next test
if today > aDate « reschedule for next month
    theTask^.taskTime = getThirdTue(month+1, year)

Missed Tasks

There is some question of what to do about tasks that may have been missed because Frontier was not running. You get some say in this by setting user.scheduler.prefs.reschedule. If you set it to true, then when Frontier starts up, if it finds that a task was missed and that its minutesBetweenRuns is greater than 60, a dialog appears letting you know that the task is ready to run; if you click OK, the task runs at the top of the next minute. Otherwise - that is, if you set user.scheduler.prefs.reschedule to false, or if a missed task's minutesBetweenRuns is less than 60, or if you click Cancel in the dialog - the task is simply rescheduled, as it would have been if it had just been performed.

Thus, regardless of how you set user.scheduler.prefs.reschedule, a one-time task missed during the night will be performed at the top of the first minute after startup. If this isn't what you want, you may have to write your own version of scheduler.monitor().

Logging

The performance of tasks (including, if there was a runtime error, the error message) is logged in an outline at user.scheduler.log - if it exists. The Scheduler comes with a default hourly task; so if you've experienced a mysterious situation where, after a save, you do nothing with Frontier, and yet when you quit Frontier, it asks whether you want to save changes (what changes?), it might be that the change is in user.scheduler.log. You may wish to check this outline from time to time; it's up to you to see that it doesn't get ridiculously long.

To turn off logging, just delete or rename user.scheduler.log.

Making a Task

There are two chief ways to make a new task. Tasks to be performed every hour on the hour and tasks to be performed at 2:00 A.M. each morning are so common that there are menu items to create them in the Scheduler menu that appears when you choose Scheduler from the Suites menu. Otherwise, the easiest way is to call scheduler.addTask() . The syntax is:

scheduler.addTask (taskTime, script, minutesBetweenRuns)

So, to install our task that puts up a dialog on the third Tuesday of each month, one might say:

scheduler.addTask(clock.now()+60, "pta()", 60)

1. Those who, after studying the Scheduler, find it inadequate to their needs, may wish to consult Preston Holmes's cron suite, at http://siolibrary.ucsd.edu/Preston/scripting/root/suites/cron.html.


TOP UP: Applied Frontier NEXT: TCP/IP

This is Frontier: The Definitive Guide by Matt Neuburg, unabridged and unaltered from the January 1998 printing.
It deals with the freeware Frontier 4.2.3; for commercial Frontier 8, look here, and for the inexpensive Radio UserLand, look here. See my Web site for information.
Those wishing an offline or printed copy may purchase the book, which is still in print.
Copyright 1998 O'Reilly & Associates, Inc. ALL RIGHTS RESERVED. Reprinted with permission.