Friday, July 15, 2011

Configuring a DirectoryScanJob in Quartz.Net 2.0

NOTE: I'm now blogging at http://jayvilalta.com/blog and not updating this blog anymore. For information on the latest version of Quartz.Net, visit me there.

In today’s post I will explain how to configure a DirectoryScanJob in Quartz.Net 2.0. Configuring the DirectoryScanJob can be done either programmatically or using the quartz_jobs.xml file. We will cover how to configure the job using the xml file in this post as it is not exactly straightforward to do so. If you’re interested in a post on how to configure the job programmatically, please leave a comment.

How to Configure the DirectoryScanJob using quartz_jobs.xml

For our example we will configure a DirectoryScanJob that looks at given directory for changes and then writes out to the log how many files were updated. Then we will add a file to the directory and we will see what happens on the scheduler. Let’s get started.
First, we’ll create the folder that will be our scan folder: C:\FolderToScan.
Now, let’s create the quartz_jobs.xml file and configure it appropriately. It will look something like this:
   1:  <schedule>


   2:   


   3:  <job>


   4:    <name>directoryScanJobExample</name>


   5:    <group>directoryScanJobExample</group>


   6:    <description>Sample job for Quartz Server</description>


   7:    <job-type>Quartz.Job.DirectoryScanJob, Quartz</job-type>


   8:    <job-data-map>


   9:      <entry>


  10:        <key>DIRECTORY_NAME</key>


  11:        <value>C:\FolderToScan</value>


  12:      </entry>


  13:      <entry>


  14:        <key>DIRECTORY_SCAN_LISTENER_NAME</key>


  15:        <value>DirectoryScanListenerExample</value>


  16:      </entry>


  17:    </job-data-map>


  18:  </job>


  19:   


  20:  <trigger>


  21:    <simple>


  22:      <name>directoryScanJobExampleSimpleTrigger</name>


  23:      <group>directoryScanJobExampleSimpleTriggerGroup</group>


  24:      <description>Simple trigger to simply fire sample job</description>


  25:      <job-name>directoryScanJobExample</job-name>


  26:      <job-group>directoryScanJobExample</job-group>


  27:      <misfire-instruction>SmartPolicy</misfire-instruction>


  28:      <repeat-count>-1</repeat-count>


  29:      <repeat-interval>10000</repeat-interval>


  30:    </simple>


  31:  </trigger>


  32:   


  33:  <job>


  34:    <name>addDirectoryScanListener</name>


  35:    <group>directoryScanJobExample</group>


  36:    <description>Sample job for Quartz Server</description>


  37:    <job-type>Examples.DirectoryScanListenerExample, Examples</job-type>


  38:  </job>


  39:   


  40:  <trigger>


  41:    <simple>


  42:      <name>addDirectoryScanListenerSimpleTrigger</name>


  43:      <group>directoryScanJobExampleSimpleTriggerGroup</group>


  44:      <description>Simple trigger to simply fire sample job</description>


  45:      <job-name>addDirectoryScanListener</job-name>


  46:      <job-group>directoryScanJobExample</job-group>


  47:      <misfire-instruction>SmartPolicy</misfire-instruction>


  48:      <repeat-count>0</repeat-count>


  49:      <repeat-interval>10000</repeat-interval>


  50:    </simple>


  51:  </trigger>


  52:  </schedule>




Lines 3-31 of the xml file are used to define the DirectoryScanJob and to set the required properties on it. The job is set to look at the C:\FolderToScan folder that we created earlier (lines 9-12). The listener that will be notified when files are updated in the folder is called DirectoryScanListenerExample (lines 13-16). This is just the listener’s friendly name (the key used to look it up actually). We could have called it Jim.  Next up is the trigger definition (lines 20-31), which creates a SimpleTrigger that fires every 10 seconds until the scheduler is stopped.


Right after the configuration of the DirectoryScanJob you will find another job configuration. This extra job is necessary because we have no way other way of adding the DirectoryScanListener to the scheduler through the quartz_jobs.xml. What we have done is created a job that runs once and adds the DirectoryScanListener to the scheduler. Let’s take a look at the code for Examples.DirectoryScanListenerExample, which is both the job that adds the listener as well as the listener itself. This is done by implementing both IJob and IDirectoryScanListener. You probably don’t want to do this in your production code, but for a quick example it works fine. Here’s the code for DirectoryListenerExample:


public class DirectoryScanListenerExample : IDirectoryScanListener, IJob
 {
    public void FilesUpdatedOrAdded(FileInfo[] updatedFiles)
    {
        logger.InfoFormat("Found {0} updated files", updatedFiles.Length);
    }

    public void Execute(IJobExecutionContext context)
    {
        logger.Info("Adding the listener to the context");
        context.Scheduler.Context.Add("DirectoryScanListenerExample", new DirectoryScanListenerExample());
        logger.Info("Added the listener to the context");   
    }

    private static readonly ILog logger = LogManager.GetLogger(typeof(JobListenerExample));
 }



This implementation of the IDirectoryScanListener is quite simple. The FilesUpdatedOrAdded method gets called when the job detects changes and it prints out to the log how many files were updated. The Execute method is called once, and here is where we add the listener to the scheduler’s context. The listener’s name is hard coded here, but you could also make that a parameter that gets passed in from the quartz_jobs.xml file via the job data map.


Running the DirectoryScanJob



Let’s start up our scheduler with this quartz_jobs.xml and see what happens. Here is the output from the scheduler’s start up process (I’ve removed the non interesting bits…).



   1:  Parsing XML file: C:\quartznet2\quartz_jobs.xml with systemId: ~/quartz_jobs.xml


   2:  Adding 2 jobs, 2 triggers.


   3:  Adding job: directoryScanJobExample.addDirectoryScanListener


   4:  Adding job: directoryScanJobExample.directoryScanJobExample


   5:  Scheduler ServerScheduler_$_NON_CLUSTERED started.


   6:  Adding the listener to the context


   7:  Job directoryScanJobExample.directoryScanJob


   8:  Example threw a JobExecutionException:


   9:  Parameters: refire = False, unscheduleFiringTrigger = False, unscheduleAllTriggers = False


  10:   Quartz.JobExecutionException: DirectoryScanListener named 'DirectoryScanListenerExample' not found in SchedulerContext


  11:     at Quartz.Job.DirectoryScanJob.Execute(IJobExecutionContext context) in C:\git\quartznet\src\Quartz\Job\DirectoryScanJob.cs:line 90


  12:     at Quartz.Core.JobRunShell.Run() in C:\git\quartznet\src\Quartz\Core\JobRunShell.cs:line 188


  13:  Added the listener to the context




Lines 1 – 5 show us the scheduler starting, the xml file being parsed and the jobs being added. So far so good. It gets a little confusing from now on because we have several threads running, but I think we can make sense of it. Line 6 tells us that the job to add the listener has kicked off. However, lines 7 – 12 tell us that the scan job is trying to run, but it fails to run because the listener is not available. That’s because the job that adds the listener is not finished yet. Line 13 tells us that the listener is ready and has been added to the scheduler. From now on, the scan job will run successfully because it has its listener in place. Let’s add a file to the folder and see what happens.


After adding a file to the folder, we see the following:


Directory 'C:\FolderToScan' contents updated, notifying listener.
Found 1 updated files



This is what we expected, so all is good. We’ve successfully configured a DirectoryScanJob and a listener using the quartz_jobs.xml.