Friday, July 15, 2011

Configuring a DirectoryScanJob in Quartz.Net 2.0

NOTE: I'm now blogging at 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>


   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>


  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>


  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>


  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.


Anonymous said...

Hi J

Could you please show me how to config Server execute a job after finish a job. Thanks.

J said...

This functionality is not available via configuration. You have to write the code yourself. You can do this by either writing a job listener or by having your jobs schedule the next job in the chain.

Anonymous said...

Its possible to add new jobs in the "call-back" method

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


i Don´t know how to generate new jobs in this delegate becouse i dont have context scheduler to add new jobs when new files are added or created, thank you very much.

Anonymous said...

I would love to see how to implement this programmatically. I tried via the ISchedulerPlugin but AddJobListener only accepts an IJobListener.

Maybe we need to override AddJobListener in the framework to accept the IDirectoryScanListener type?

Ken said...

Hi J

Great work here but I am running as a windows service & Ado so I would like to implement this programmatically. I am struggling to see how I add the listener to the context.

Great work - Thanks.