Version 19 of file and directory change notifications

Updated 2003-11-15 22:08:34

File and directory change notifications/events are useful for (G)UIs showing a list of files, for mirroring directory(trees), tail applications, and some other things I can't think of right now. I asked if anyone knew of an extension for win32 that uses the directory notification API of windows. The quick consensus on the chat was that there wasn't one.

It'd also be nice to have this functionality for a lot of platforms. Pat Thoyts quickly found FindFirstChangeNotification and friends on MSDN and created a working extension.

-- 20Aug03 PS


Generic Solution Not a complete solution, but on the systems where [file mtime] returns the last update of a directory/file a reasonably efficient solution is just glob all files in a directory tree and storing their mtime. If you are only interested in create/rename/delete events (like most UIs are) you only need to call [file mtime $dir] to see if a file has been created/renamed/deleted since you last checked. Doing this every two seconds gives good interactive response in most cases (for UIs)

US Just to emphasize: This does not work for write/update operations on a file!

 namespace eval fschanges {        
    if { 0 } {
        #if debugging:
        interp alias {} [namespace current]::dputs {} puts
    } else {
        proc dputs { args } { }
    }

    variable watchId 0

    proc watch { file_or_dir } {
        variable watchId
        incr watchId

        upvar [namespace current]::watch$watchId watching
        if { [info exists [namespace current]::watch$watchId] } {
            array unset [namespace current]::watch$watchId
        }

        set watching(watching) [list]


        if { [file isdir $file_or_dir] } {
            addDir watch$watchId $file_or_dir 
        } else {
            add watch$watchId $file_or_dir
        }

        #set initial scan time
        set watching(last) [clock seconds]

        return watch$watchId
    }

    proc add { id name } {
        dputs "add $name"
        upvar [namespace current]::$id watching

        if { [info exists watching(watch.$name)] } {
            dputs "add exists $name"
            #no watching twice!
            return
        }

        lappend watching(watching) $name [file isdir $name]

        #and determine initial time (if any)
        if { [file exists $name] } {
            set itime [file mtime $name]
        } else {
            set itime 0
        }

        set watching(watch.$name) $itime

        return $name
    }

    proc addDir { id dir } {
        dputs "Add dir $dir"
        upvar [namespace current]::$id watching
        if { [info exists watching(watch.$dir)] } {
            dputs "Adddir exists $dir"
            #no watching twice!
            return
        }


        #puts "Add dir $dir"

        lappend new [add $id $dir]
        #puts "glob: [glob -nocomplain -path $dir/ *]"
        foreach file [glob -nocomplain -path $dir/ *] {

            if { [file isdir $file] } {
                dputs "Recurse into $file"
                set new [concat $new [addDir $id $file]]
            } else {
                lappend new [add $id $file]
            }
        }
        return $new
    }

    proc newfiles { id time } {
        upvar [namespace current]::$id watching
        set newer [list]
        foreach {file isdir} $watching(watching) {
            if { $watching(watch.$file) >= $time } {
                lappend newer $file
            }
        }
        return $newer
    }

    proc changes { id } {
        upvar [namespace current]::$id watching
        set changes [list]
        set new [list]
        #puts $watching(watching)
        foreach {file isdir} $watching(watching) {
            #puts "$isdir && [file mtime $file] > $watching(watch.$file)"
            if { $isdir && [file exists $file] && [file mtime $file] > $watching(watch.$file)} {

                set watching(watch.$file) [file mtime $file]

                lappend changes $file update

                foreach item [glob -nocomplain -dir $file *] {
                    if { ![info exists watching(watch.$item)] } {
                        if { [file isdir $item] } {
                            set new [concat $new [addDir $id $item]]
                        } else {
                            lappend new [add $id $item]
                        }
                    }
                }
            }            
        }
        foreach item $new {
            lappend changes $item created
        }

        return $changes

    }
    namespace export watch changes newfiles
 } 
 package provide fschanges 0.5

Sample usage

 package require fschanges
 namespace import fschanges::*

 #watch a directory:
 set w [watch /tmp]

 puts "Files created within the last hour: [newfiles $w [expr [clock seconds]-3600]]"

 exec touch /tmp/testfile

 #Show the new file and directory update:
 puts [changes $w]

 file delete -force /tmp/testfile

 #file deletions are not noted (yet) but it will show an updated directory.
 puts [changes $w]

Platform specific Which platforms can do this? It would be nice to create a (core?) extension to do this.

  • Windows 95, 98, ME, NT 2000, XP: Yes, FindFirstChangeNotification() and NTFS can do [file mtime $dir]
  • Windows CE: unknown but also likely to support FindFirstChangeNotification.
  • Linux <2.2: Unknown, but ext2 can do [file mtime $dir]
  • Linux 2.2+: Yes, see linux/Documentation/dnotify.txt using fcntl(fd, F_NOTIFY,DN_MODIFY|DN_CREATE|DN_MULTISHOT); See directory notification package in Tcl for more.
  • FreeBSD: Yes, kqueue: http://people.freebsd.org/~jlemon/papers/kqueue.pdf
  • OpenBSD: Supports kqueue in version 3.1 (unknown when first supported)/
  • (Other)BSD: Same as FreeBSD?
  • Classic Mac: Unknown
  • MacOSX: Supports kqueue starting with 10.3
  • Solaris: stevel thought so.
  • HP-UX: unknown
  • irix: yes. there's a file monitor.
  • dec: unknown
  • others: ???

elfring 26 Aug 2003 How do you think about the tool "File Alteration Monitor and Inode Monitor" (http://oss.sgi.com/projects/fam/links.html )? I do not know when a TCL programming interface will be available for it.

ps 27 Aug 2003 Well, by the looks of it, the IMon project is complementary to the DNotify stuff, and will probably be used if we get round to make a Tcl package.

elfring 1 Nov 2003 Can the function library "liboop" help to dispatch file events easier?