Mattk470 has asked for the wisdom of the Perl Monks concerning the following question:

I'm on a windows machine (XP) and eventually I’d like to have a file or text emailed to me as it appears in a directory.

The emailing part is fine. What is the most efficient way of starting a script when the file appears in a directory so that it's not soaking up resources in the meantime? It doesn't necessarily have to be immediately sent, but within an hour would be nice.
  • Comment on starting a script when a file appears in a directory

Replies are listed 'Best First'.
Re: starting a script when a file appears in a directory
by johnnywang (Priest) on Apr 20, 2005 at 04:20 UTC
    This is what the module Win32::ChangeNotify is for, it has a wait() function which blocks until the directory content changes, you can then send your email.
Re: starting a script when a file appears in a directory
by davidrw (Prior) on Apr 20, 2005 at 00:30 UTC
    Two primary methods immediately come to mind:
    • Start a daemon type script that always sits there running, and it checks, sleeps, checks, sleeps, etc
    • Have a script that runs once and checks, and execute it periodically with cron or windows scheduler or whatever you have a vailable on XP.
    Assuming you have a scheduling means, I would say the second is the better method -- something isn't always running (your efficiency concern), and it's more robust becuase you don't have to worry about the job getting hung or killed.

    Two methods pop to mind for the actual "checking" part, too:
    • Check the directory for files created in the last N minutes.
    • Keep a state file (maybe look at Cache::FileCache; and also useful related info in the recent best way to keep a simple count? about storing data between runs) that is the last time you ran and what files existed, and compare against that. (or store the last timestamp you checked, and check for files created affter that)
    Be sure to reference the File::Find module as well as the -X and stat sections in the perlfunc manpage.

    Update: Here's an actual one-liner solution i used out of a linux crontab once (convenient cause cron takes care of emailing you iff there's output)--this is the shell script wrapping it (I don't necessarily recommend this, but might have some reference value):
    # USAGE: touchedCheck filename [minutes] # # Exit Value: 0 if file was touched; 1 if not touched. # # Will print info about the file iff it changed in the last N minutes. # Otherwise prints nothing. # # Intended for crontab use, such as: # 0 * * * * touchedCheck /tmp/some_file.txt 60 # /usr/bin/perl -e '$f=shift;$N=shift||60; printf("%s was changed %d min +utes ago.\n%s", $f, $x/60.0, `/bin/ls -lart $f`) && exit(0) if ($x = +(time - (stat($f))[9])) <= 60*$N; exit(1)' $1 $2
Re: starting a script when a file appears in a directory
by calin (Deacon) on Apr 20, 2005 at 09:43 UTC

    Also, you must be aware that accessing a file by two or more processes while one or more of these processes does modifications opens the door to race conditions.

    The most common symptom you're likely to see, particularly with large files and slow updates, is that you'll get truncated files.

    Race conditions on filesystem resources are generally avoided by implementing a file locking scheme. Basically, your writer process needs to acquire a write lock and your checking / e-mailing process(es) needs a read lock. Be aware though that locking is a complex subject and it can get quite messy, and many things can go wrong. Google and Super Search are your friends.

Re: starting a script when a file appears in a directory
by hubb0r (Pilgrim) on Apr 20, 2005 at 01:47 UTC
    I would suggest that you write a daemon that uses the FAM (file access monitor) Module CPAN) and when it sees a change it calls the script.

    I do have to preface this with the fact that I did try to use the FAM module early on in my learning of perl and was unsuccessful in getting it to work, but I believe that that was probably a symptom of my newbie-ness and not the ability of the module to do the job!
      IIRC FAM has to do with SGI/Irix and while it's obviously sensible to at least mention it in this context, I suppose that at best it is portable to other *NICES (but I would be glad to be disproved!) OTOH the following answer, mentioning a Win32 specific module seems much more on topic.

        SGI::FAM definitely does work on Linux (see for instance Re: pause/wait command? ) and as FAM itself is open source it is possible that it has been ported to other OS too

        /J\

        Agreed. I glossed over the fact that it was on win32 when I replied.

        And yes, it definitely does work! I decided to play around with it last night after posting and got it to do exactly what I expected it to. I must say though that the errors that it throws get a little confusing because it has a lot of xs code in it.
Re: starting a script when a file appears in a directory
by Mabooka-Mabooka (Sexton) on Apr 20, 2005 at 18:43 UTC
    All suggestions look OK, except... as far as I know, there's no way to create a true daemon under Windows: there's no fork()..:-).

    So, I see two choices: 1) spend a week on writing (and three more on debugging) a Win32 service (cli/srv with pipes and msgs and stuff);

    2) write
    while(1) { if new_files(dir): send_m(..); bzz(60*60); }
    -- and check periodically that it's running...:-).

    For not to do it manually, you'll need another program usually called watchdog. Not sure if there's a "crontab" analog under W. If not, you could also put watchdog over watchdog over watchdog to increase the chances of the system always running, - unless you get another process crashing or memory-leaking or a virus or an Automatic Windows Update.:-)).

    // Note that sleep doesn't "soak up resources in the meantime". Careful with memory alloc-n between cycles though.