Introduction
While there could be the approach of using the Windows Scheduler to execute those tasks, it is always interesting to be able to control everything from within VFP. A small VFP scheduler can be really useful when you have a series of tasks that should be executed coming in from several applications. Those tasks can include various processes such as sending emails, making backups of DBF files, processing the automatic renewals of credit card payments at midnight, refreshing the banners on your Web sites and calculating various Web site affiliate payments.
The infrastructure
The idea is to have an application that sits on a server which takes responsibility for all that. This application should be a small framework taking decision on what to do and when. It should be able to read parameters from a file and act accordingly.
I always like the idea of using a Main.ini file so my applications can read the parameters from. This could easily be adjusted to read from the registry, from an XML file or from any other source. The goal is to be able to easily adjust the application parameters, when there would be a need to, without having to recompile the application.
The Main.ini file should contain the generic parameters as well as application specific parameters. For example, a generic parameter could be a temporary directory that can be used by several tasks from various applications while an application specific parameter could be the Url of the domain related to one specific application.
Here is an overview of a Main.ini file:
[System] SmtpHost=192.168.1.9 Client=http://www.universalthread.com Cgi=http://www.universalthread.com/wconnect/wc.dll? Temp=d:\Temp Timeout=60 Table=4 Email=2 Application=2 [Table] Table1=d:\Robot\task Table2=d:\Robot\Robot Table3=d:\Robot\Backup Table4=d:\Robot\RemoveFile [Email] Email1=d:\Data\UniversalThread\email Email2=d:\Data\DMA\email [Application1] Exe=Universal Thread Title=Universal Thread Http=http://www.universalthread.com Dbf=d:\Data\UniversalThread Fat=d:\iis\UniversaThread Backend=1 [Application2] Exe=Direct Martial Arts Title=Direct Martial Arts Http=http://www.directmartialarts.com Fat=d:\iis\direct martial arts Cgi=http://www.directmartialarts.com/wconnect/wc.dll? ClientServer=1 Backend=1 ConnectionString=Driver={Microsoft Visual FoxPro Driver};SourceType=DBF;SourceDB=d:\Data\DMA; Exclusive=No;Collate=Machine;NULL=NO;Deleted=Yes;BACKGROUNDFETCH=NO
So, basically, in that example, the scheduler is responsible to execute tasks from two applications. The first application is for the Universal Thread and the second application is for a new site called Direct Martial Arts. As those applications are not related, they are handled as two different applications. So, we can isolate the business logic in two separate EXEs that would be called by the scheduler. As the scheduler acts as a framework for scheduling processes, we only have to code what is necessary for each task we have to execute and do not have to worry about the scheduling aspect of the infrastructure.
The scheduling table
The scheduling table contains the parameters for each task the scheduling application has to execute. That table is containing tasks for all applications. Thus, a field in each record is used to identify the application. So, if we have 10 tasks for the Universal Thread, they will be executed in sequence. Then, the scheduling application will proceed with the tasks for the Direct Martial Arts site.
This table contains fields which describe the task, the PRG to call, when the task should be executed on so on. Here is the structure of that table:
The following provides an overview of a task table:
Some columns have been resized to fit the article spacing. For example, the AddDate and ModDate fields contain the time but they are not shown in this image.
Overview of the scheduling application
While there could be a way to define this scheduling application as a Windows Service, in this article, I will simply consider the application to be launched manually from the desktop. When it is run, it looks like this:
Basically, the application sits on the desktop and waits for execution. The Main.ini file contains a parameter in the System section named Timeout. This is the interval of executing the scheduling monitor process.
Once the interval is reached, the loop of tasks to execute is done. When this is the case, the scheduling application will show the status of the current task being executed:
The scheduling application contains a button to force an immediate process. This is useful when you are on the server and you wish to force the execution of the list of tasks to be executed for testing purposes. Thus, in this case, you will not have to wait for the interval to be reached as the tasks related to the current time will be executed. This means that if the time is 17h00, all the tasks scheduled for All, Quaterly and Hourly will be executed. Assuming 17h34 will be the current time, and you made modification in the code for a task that should be executed on an hourly basis, you would simply have to update the Task.dbf table to check temporary the All field so when you test it, or when the next interval will be reached automatically, that specific task will also be executed. Once the test completed, you may uncheck the All field so that specific task will be executed as scheduled, thus at the next hour.
Generic tasks
There are some generic tasks that do not require an entry in the Task.dbf table. These tasks are common to several applications and can be defined at the higher level of this structure such as in the framework level, thus the scheduling application. Those tasks are responsible for sending emails, making backups and removing temporary files. Other generic tasks can be added based on your own requirements. I have found that over the years, those three are commonly used in all of my applications.
At each interval, additionally to executing the records of the Task.dbf table that are marked as All, I also execute the task of sending emails. As this task is generic, there is no entry in the Task.dbf table. Each application has its own Email.dbf table. Thus, based on the Main.ini file, the location of each of those email tables is known. The scheduling application is then looping on each of them and sending the emails that have to be sent.
Every 15 minutes, additionnaly to executing the record of the Task.dbf table that are marked as Quaterly, I also execute the task of removing temporary files. If applicable, those files are files that other applications will create for a certain period but can be removed after a certain time. One example is a file that can be created on a directory of a Web site for a certain download. Based on the definition of each record in RemoveFile.dbf, the scheduling application knows after which duration each file in a directory has to be deleted.
Here is the structure of the RemoveFile.dbf table:
The following provides an overview of a RemoveFile.dbf table:
Next, we have a very interesting task. It is the task to backup the application data at midnight. This one uses a COPY TO approach so a backup can be done without interrupting the applications. It may take a little bit longer but this is not a factor as this task is done from the scheduling application. Its role is to copy into a backup directory all application data under its own directory naming convention. Thus, once the process is done, there is always a fresh copy of the data residing in another directory where only DBF are copied. Additionally to that, a Reindex.prg file is created in case reindexation would be needed.
In order to achieve that task, we also need a Backup.dbf table. Its structure is really simple. Only one field is required. Here is it:
The following provides an overview of a Backup.dbf table:
As you might have seen, we also backup the Robot directory, which is the directory of the scheduling application. Thus, the Task.dbf, RemoveFile.dbf and Backup.dbf tables are part of the backup process as well.
Quaterly, Hourly and Daily tasks
We need to have some kind of mechanisms to assure that the quarterly, hourly and daily tasks will only be executed once as per their actual timeframe. By that, I mean that a quarterly task should only be executed once per 15 minutes. The same goes with an hourly task which should only be executed once per hour. In order to accomplish task, I have a Robot.dbf table which includes flag for such information.
Here is the structure of the Robot.dbf table:
The following provides an overview of a Robot.dbf table:
The application EXE
The scheduling application will call in loop all application EXEs defined in the Main.ini file. In our example, we have two applications to call. Those application role is simply to launch the process of all task by simply calling the ProcessAllTask() function, which is available in the calling EXE, thus the scheduling application. The application can also set up variables that need to be visible in this EXE and so on. We assume that the application contains the functions and/or PRGs that need to be available based on the Task.dbf table definition.
Thus, an application can have a project with Master.prg as the main PRG which can simply contain one line, in its simplest way, thus ProcessAllTask(). The application project would then include the PRGs necessary for all task executions.
So, in memory, the scheduling application is always running. This is a VFP EXE that is launched manually and sits on the desktop. Then, at specific interval, it calls in loop each application EXE defined in Main.ini. So, this is nice because every time you want to update a task or create a new one, you only have to deal with a small project, thus the one you want for such maintenance. This also allows multiple developers to build their own application EXE without knowing anything about the scheduling application. As everything is done in VFP, it also gives you full flexibility on what you want to achieve. It is also possible to update the application EXEs without interrupting the scheduling application as those are sub EXEs being called from the scheduling application. In case of collision, assuming that the interval is being kicked and is in its process of executing the tasks, within a manner of seconds, you should be able to get a handle on the application EXE you wish to update.
Conclusion
This article provided an overview of task scheduling in VFP. I hope it gave you some ideas about implementing such functionalities if you do not already have some.
Source code
It is not possible to include the full source code of the scheduling application. However, it is possible to include the ProcessAllTask() function which provides the core functionality of the scheduling application:
* Process all tasks FUNCTION ProcessAllTask LOCAL lnOldSel,lnMinute,lnQuaterly,lnHour lnOldSel=SELECT() * Process task for each interval SELECT Name,Function FROM Task WHERE NoSystem=goRobot.nSystem AND All INTO CURSOR TempTask ProcessTask() * Quaterly lnMinute=MINUTE(DATETIME()) DO CASE CASE lnMinute>=0 AND lnMinute<=14 lnQuaterly=0 CASE lnMinute>=15 AND lnMinute<=29 lnQuaterly=15 CASE lnMinute>=30 AND lnMinute<=44 lnQuaterly=30 CASE lnMinute>=45 AND lnMinute<=59 lnQuaterly=45 ENDCASE SELECT Robot SEEK goRobot.nSystem ORDER TAG NoSystem IF Quaterly<>lnQuaterly REPLACE Quaterly WITH lnQuaterly SELECT Name,Function FROM Task WHERE NoSystem=goRobot.nSystem AND Quaterly INTO CURSOR TempTask ProcessTask() * Additionnaly to the quaterly tasks, we have to remove the files RemoveFile() ENDIF * Hourly SELECT Robot lnHour=HOUR(DATETIME()) IF Hourly<>lnHour REPLACE Hourly WITH lnHour SELECT Name,Function FROM Task WHERE NoSystem=goRobot.nSystem AND Hourly INTO CURSOR TempTask ProcessTask() ENDIF * Daily SELECT Robot IF Daily<>DATE() REPLACE Daily WITH DATE() SELECT Name,Function FROM Task WHERE NoSystem=goRobot.nSystem AND Daily INTO CURSOR TempTask ProcessTask() * Additionnaly to the daily tasks, we have to do the backup Backup() ENDIF SELECT(lnOldSel) FUNCTION ProcessTask LOCAL lcExec SCAN Main.StatutClient.Caption=ALLTRIM(Name) lcExec=Function &lcExec ENDSCAN