Now that ColdFusion 8 gives us a crap load of image functions as well as event gateways in all editions, I thought I'd write up a super quick demo on how you can use both in your application. If you've never played with event gateways before, either because you thought they were too complex or you were running the Standard edition of ColdFusion, you should really take a look now. Event gateways are extremely powerful - but not as complex as you may think.
Before I begin - please check the docs on event gateways for a full explanation. I'm just writing a quick example here and won't be covering all the details.
The gateway I want to talk about today is the Directory Watcher. This gateway lets you, obviously, monitor a directory. You can have ColdFusion notice a change to the directory. This change can either be a new file, a modified file, or a deleted file.
So what are we going to do with our gateway? Our client, Hogwarts Press, Inc., has a group of reporters who handle press relations for the Hogwarts school. Being non-techies, they just want to take pictures. They can't be bothered to change these pictures for web publication.
To make things easier then we've set up a simple FTP connection for them to send their files to. They will download the pictures off their camera and FTP them up to a folder. (FTP is probably too much for them. You could also imagine an AIR application that lets them just drop files onto an icon.) The pictures will all be stored here:
/Users/ray/Documents/Web Sites/webroot/testingzone/dirwatcherimage/spool
Our code needs to:
- Check the file to ensure it is an image. (You never know with those non-techies.
- If an image, resize to a max width and height of 500 each. (Of course, you could also do other things like change the quality.)
- Move the image to a folder named 'ready.'
- If the file wasn't an image, delete it.
So let's start off by creating an instance of the DirectoryWatcher gateway. This is done in the ColdFusion Administrator. In order to do it, though, you need to specify a CFC and a configuration file. I created two empty files, watcher.cfc and config.ini. The figure below shows the values I set for my gateway. The name isn't important - but should reflect what your gateway is doing, or the application it is working with.

Now we need to edit the config file. This file is used by the event gateway to control the behavior of the code watching the directory. As I mentioned above - the gateway can notice adds, edits, and deletes. All I really care about is the add, so my config file looks like so:
# The directory you want to watch. If you are entering a Windows path
# either use forward slashes (C:/mydir) or escape the back slashes (C:\\mydir).
directory=/Users/ray/Documents/Web Sites/webroot/testingzone/dirwatcherimage/spool
Should we watch the directory and all subdirectories too
Default is no. Set to 'yes' to do the recursion.
recurse=no
The interval between checks, in miliseconds
Default is 60 seconds
interval=6000
The comma separated list of extensions to match.
Default is * - all files
extensions=*
CFC Function for file Change events
Default is onChange, set to nothing if you don't want to see these events
changeFunction=
CFC Function for file Add events
Default is onAdd, set to nothing if you don't want to see these events
addFunction=onAdd
CFC Function for file Delete events
Default is onDelete, set to nothing if you don't want to see these events
deleteFunction=
Notice the addFunction line. This simply says that the gateway should run the onAdd method of my CFC. Now let's take a look at the CFC:
<cfcomponent>
<cffunction name="onAdd">
<cfargument name="cfevent">
<cfset var myfile = arguments.cfevent.data.filename>
<cfset var image = "">
<cfset var newdest = getDirectoryFromPath(myfile)>
<cfif not isImageFile(myfile)>
<cflog file="dirwatcher" text="#myfile# is NOT an image">
<cffile action="delete" file="#myfile#">
<cfreturn />
</cfif>
<cflog file="dirwatcher" text="#myfile# is an image">
<!--- resize --->
<cfset image = imageRead(myfile)>
<cfif image.width gt 500 or image.height gt 500>
<cfset imageScaleToFit(image,500,500,"highestquality")>
<cflog file="dirwatcher" text="Resized to 500x500">
</cfif>
<cfset imageWrite(image, myfile)>
<!--- copy to ready --->
<!--- newdest is the same path as spool, so 'cheat' and switch to ready --->
<cfset newdest = replace(newdest, "/spool", "/ready")>
<cffile action="move" source="#myfile#" destination="#newdest#/#getFileFromPath(myfile)#">
<cflog file="dirwatcher" text="Moved to #newdest#/#getFileFromPath(myfile)#">
</cffunction>
</cfcomponent>
The first thing I want you to note is the argument: cfevent. When the gateway "talks" to my CFC, it will pass one argument, CFEVENT, that contains information about the event. In particular, the "data" key contains 3 values: filename, type, and lastmodified. The filename is obviously the filename. The type refers to the type of operation, and will either by ADD, CHANGE, or DELETE. Why is this even needed when I'm in an onAdd event? Nothing prevents me from pointing both the change and add functions to the same CFC method. My code could then check the value to see exactly what is going on.
So note then that I get the file out of the data. The rest of the code is rather simple. I check and see if the file is an image. If it isn't - I delete and leave the CFC. If it is - and if the image is too big - I resize it.
Note the use of cflog. All of this code runs behind the scenes. No web pages are viewed in this process. Therefore I used cflog so I could monitor what was going on.
As the web developer, what's nice is that I can now just look at my "ready" folder and put the web-ready images up on the web site.
As a few last notes:
- The code would be better if it handled name conflicts better.
- As mentioned, "web ready" means more than just shrinking. I was just trying to keep things simple.
- I mentioned FTP or an AIR app, but anything could drop files into this folder.
That's it. Hopefully folks find this useful!
Archived Comments
Somebody found the Harry Potter books....;)
Somebody found them and read them all over a 2 month period. Don't laugh - I'm close to renaming myself "ColdFusion Wizard". Although "ColdFusion Death Eater" sounds cooler.
I did something similar to this when CF7 first came out. Because my mail server was on my web server, I setup directory watcher to watch an email attachment folder. The folder was tied to an email I only sent photos from my camera phone to. I then used a custom tag (pre-cfimage of course) to resize those and also create thumbnails and automatically put them up onto my website. Although there are tons of places you can upload camera phone photos to nowadays, I'm sure that little app would've been much nicer in CF8 especially with the new cfimage. Great tutorial and intro to the DW event gateway! Thanks!
event gateways in all editions???
Schweeet!
;-)
Nice! I haven't used event gateways yet, I knew they were there but I never really paid attention to what they were, but now I can see that I have been missing out. Now that I'm taking the time to read about them, I have already thought of a few things I can use them for. Thanks!
Very cool example of two cool CF features.
Nice tip about using cflog with the directory watcher gateway. I set up one recently and didn't think of doing something like that.
Hello Ray,
I tried to implement a feature similar to yours but ended up not doing it do to a problem I encountered (Windows Only Testing). In a scenario where FTP is involved a file is automatically placed in the folder when an upload is executed. Therefore, if your client is uploading a 25mb file and your directory watcher runs while only 10k has been uploaded it will set of an ADD EVENT. Of course at this point you would then try to run your code and then would fail because the file was not complete. The one good thing is that an event would be called again when the file finishes uploading (sends a CHANGE EVENT). Therefore while writing this message I realized hey why not just wrap a cftry around my function and if it fails well ignore and continue, since it would be called again on the CHANGE EVENT. Of course you would have to tie the same function to your ADD and CHANGE event and if that is ok then there is no problem.
Instead I just created a scheduled task that looks at the directory and compares the contents based on the last read and when it find a file which has not changed (upload complete) it runs the task.
See my task waits for a zip file containing an upload and a pdf form generated by the CRM. The app reads the zip makes sure contents are there, reads the pdf and based on the info provided in the pdf, enters info into db and moves the contents of the zip to the appropriate directory and deletes the zip uploaded.
My example can work with either the gateway or the scheduled task. Funny how sometimes you just need to type things out and the answer comes right back at you. Oh well, I already wrote both so either will work.
Ah good point Giancario - and I now remember reading about this exact same problem. You could always just use the scheduled task to copy the files to the folder the EG uses. Why do that instead of just using the scheduled task to do it all? It would allow me (site admin) to drop files in manually via Copy/Paste, and not FTP.
Hello Ray,
The Scheduled task does do it, the only thing is that there is up to a 3:00 minute delay for processing since I run it at 1.5 min intervals. Basically when you add a file and the ST runs it adds the file to a saved query and then will have to compare a minute and a half later. SO you could drop files into the folder but the processing time may take longer.
The thing is that if a file is corrupt / broken on upload and CF creates an error I log it and move the file to a rejectedUploads directory. So if it fails on the add event I may enter an unnecessary log entry (I am guessing I will not be able to move the file because it should be locked by the FTP process).
At this point I am bothered by the delay, so again with a little bit of typing I think what I will do is make sure to ignore a read error on the add event and place the processing CFC back to work on the EG rather than the ST.
The process should run again onChange event which at that point I will process all error properly. THe only thing I see happening here is probably having some files that failed on the add event and never ran an onChange event. WHich I can then just have a ST clean up once a day.
Going to test now .....
It worked .... back to EG processing only.
Hey Ray, I'm having trouble getting the event to fire...I've created a Gateways folder under the webroot, and have the listener CFC and gateway config file in it. Also put in an application.cfc that will email me whenever onError or OnRequestStart are fired. But whenever I upload a new file (or changed file) into the monitored folder, nothing happens. (I've got both the addFunction and changeFunction (in the config) calling the same CFC method). If I call another template in the folder, though, I get the email from the OnRequestStart -- so I know the application.cfc is working. Any ideas what else to check?
Yes, the eventgateway and exception logs.
Ray,
I am having a similar problem as David (above) is having. The gateway instance status is "Running", only the CFC method is not invoked when a file is added to the folder. You suggested looking in the eventgateway and exception logs, but I find no errors associated with the gateway. The logs do however indicate gateway initilization, but nothing further.
Per David's reply, an EG call is NOT a web call. Application.cfc will only run for web calls, not EG calls.
Maybe try using cflog in your CFC methods to see if they are being run.
Thanks for the reply! I just verified the CFC methods are not being invoked by the gateway. This is pretty puzzling for me as I am not VERY familiar with Java. This should have been a breeze for me.
1. gateway services are enabled.
2. gateway instace:
Gateway ID ----> Watcher Test
Gateway Type ----> Directory Watcher
CFC Path ----> C:\Inetpub\wwwroot\tests\watcher.cfc
Configuration File ---->C:\Inetpub\wwwroot\tests\config.cfg
Status ----> Running
3. Config:
# The directory you want to watch. If you are entering a Windows path
# either use forward slashes (C:/mydir) or escape the back slashes (C:\\mydir).
directory=C:/Inetpub/wwwroot/tests/watch
# Should we watch the directory and all subdirectories too
# Default is no. Set to 'yes' to do the recursion.
recurse=no
# The interval between checks, in miliseconds
# Default is 60 seconds
interval=10000
# The comma separated list of extensions to match.
# Default is * - all files
extensions=*
# CFC Function for file Change events
# Default is onChange, set to nothing if you don't want to see these events
changeFunction=
# CFC Function for file Add events
# Default is onAdd, set to nothing if you don't want to see these events
addFunction=onAdd
# CFC Function for file Delete events
# Default is onDelete, set to nothing if you don't want to see these events
deleteFunction=onDelete
--------------------------------------------------
4. CFC:
<cfcomponent>
<cffunction name="onAdd" access="remote">
<cfargument name="CFEvent" type="struct" required="yes">
<cflog file="Watcher" text="File Added">
</cffunction>
<cffunction name="onDelete" access="remote">
<cfargument name="CFEvent" type="struct" required="yes">
<cflog file="watcher" text="File Deleted">
</cffunction>
</cfcomponent>
Sorry for the long post. I have it about as simple as it could get and can't get it working. Let me know if you can think of anything else.
Dumb question - but do you see that the interval is 10000 seconds? That's a HUGE interval. Close to 20 minutes. If you change it to 10 and test again, do you see results?
Ray,
Thanks for the suggestions. I was able to get this working:
1. Stripped config file down to the following:
directory=C:/Inetpub/wwwroot/tests/watch
interval=10000
2. Added access="public" to CFC and each CFC functions (onAdd, onChange, onDelete).
Not sure which of the two changes made the fix. I'll have to back and try to remove the access properties to verify.
As far as the interval=10000 goes, I am getting a directory check every ten seconds. I am convinced the interval is measured in milliseconds. If my math serves me correctly 10000/1000 equals 10.
Again, thank you so much for your help!
Duh, it plainly said milliseconds and I misread it. Sorry. :)
Happy you got it working!
I can not find CF Admin > Data & Services > Event Gateways > Gateway Instances option in my local CF admin panel.
Is there anything that I need to install/configure?
Do you have CF Standard? It's only available in Enterprise.
Sorry - I was on crack. EGs are available in standard, just limited in terms of performance. You will find them NOT under Data and Services, but a root level item, Event Gateways.
Hi
i am trying to get this to work however i to not see any results even though the gateway is running in cfadmin the in /out values remain 0
and it does not seem to be creating the log file
Here is my cfc i simply have removed the image editing code as i want to be able to use this for a different application
<cfcomponent>
<cffunction name="onAdd">
<cfargument name="cfevent">
<cfset var myfile = arguments.cfevent.data.filename>
<cfset var newdest = getDirectoryFromPath(myfile)>
<cflog file="dirwatcher" text="#myfile# is valid">
<!--- copy to ready --->
<!--- newdest is the same path as spool, so 'cheat' and switch to ready --->
<cfset newdest = replace(newdest, "/xmldata", "/xmldata/processed")>
<cffile action="move" source="#myfile#" destination="#newdest#/#getFileFromPath(myfile)#">
<cflog file="dirwatcher" text="Moved to #newdest#/#getFileFromPath(myfile)#">
</cffunction>
</cfcomponent>
the config file is
# The directory you want to watch. If you are entering a Windows path
# either use forward slashes (C:/mydir) or escape the back slashes (C:\\mydir).
directory=C:/home/svr.trackingcentral.com.au/...
# Should we watch the directory and all subdirectories too
# Default is no. Set to 'yes' to do the recursion.
recurse=no
# The interval between checks, in miliseconds
# Default is 60 seconds
interval=6000
# The comma separated list of extensions to match.
# Default is * - all files
extensions=*
# CFC Function for file Change events
# Default is onChange, set to nothing if you don't want to see these events
changeFunction=
# CFC Function for file Add events
# Default is onAdd, set to nothing if you don't want to see these events
addFunction=onAdd
# CFC Function for file Delete events
# Default is onDelete, set to nothing if you don't want to see these events
deleteFunction=
Nothing seems to come to mind - but I can say I haven't used this code since 2007. Did you try the eventgateway.log file?