tailf-inotify

This is a more efficient implementation of the tailf functionality based on the tcl-inotify package. It has the advantage that it doesn't suffer from polling overhead as the other implementations. Inotify asynchronously informs tailf of write changes in a file.


#!/bin/tclsh
# An efficient tailf implementation using inotify
package require inotify
package provide tailf

namespace eval tailf {
namespace export add rem

array set File2wd {}
array set Wd2info {}

inotify create ::tailf::watch ::tailf::handler

proc getpos {filepath} {
    set fd [open $filepath r]
    seek $fd 0 end
    set pos [tell $fd]
    close $fd
    return $pos
}

proc handler {args} {
    variable File2wd
    variable Wd2info

    set events [watch read]

    foreach {wd flags cookie filepath} $events {
        if {! [info exists Wd2info($wd)]} {
            return
        }

        set diff {}
        set callback [lindex $Wd2info($wd) 1]
        set filepath [lindex $Wd2info($wd) 2]
        set old [lindex $Wd2info($wd) 0]
        set new [getpos $filepath]
        set Wd2info($wd) [lreplace $Wd2info($wd) 0 0 $new]

        if {$new < $old} {
            set old 0
        }

        if {$old != $new} {
            set fd [open $filepath r]
            seek $fd $old
            while {[gets $fd line] >= 0} {
                append diff "$line" "\n"
            }
            close $fd
        }

        eval [list $callback $filepath $diff]
    }
    return
}

proc add {filepath callback} {
    variable File2wd
    variable Wd2info

    if {[file exists $filepath] && [file isfile $filepath]} {
        set pos [getpos $filepath]
        set wd [watch add $filepath {w}]
        set File2wd($filepath) $wd
        set Wd2info($wd) [list $pos $callback $filepath]
    } else {
        error "is either not a file, or does not exist"
    }
}

proc rem {filepath} {
    variable File2wd
    variable Wd2info

    if {! [info exists ::tailf::File($filepath)]} {
        error "$filepath is not monitored, or watch has been removed"
    }

    set wd $File2wd($filepath)
    unset Wd2info($wd)
    unset File2wd($filepath)
    watch del $filepath
}

} ;# namespace end

proc puts_hdlr {filepath diff} {
    puts -nonewline $diff
}

::tailf::add "delme.txt" puts_hdlr

set a {}
vwait a

fr - 2010-09-29 08:46:34

inotify 1.3 with 8.6b1.1 requires changes in proc handler

set events [join [watch read]]

foreach {k wd k flags k cookie k filepath} $events {