Posted to tcl by patthoyts at Sun Feb 24 22:43:47 GMT 2008view pretty

# resolver.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sf.net>
#
#	This package performs hostname resolution using a child process
#	to perform the name lookup to avoid blocking the Tcl process
#	during the blocking gethostbyname() C library function.
#
#	Usage is a bit like the http package. You are responsible for
#	calling 'cleanup' on the token once you have finished.
#
# -------------------------------------------------------------------------
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# -------------------------------------------------------------------------
# $Id: resolver.tcl,v 1.1 2004/08/26 09:47:49 pat Exp $

namespace eval ::resolver {
    variable version 1.1.0
    variable uid; if {![info exists uid]} { set uid 0 }
    variable pipe; if {![info exists pipe]} { set pipe {} }
    variable queue; if {![info exists queue]} { set queue {} }
    variable waiting; if {![info exists waiting]} { set waiting {} }

    namespace export gethostbyname result wait cleanup
}

# -------------------------------------------------------------------------

proc ::resolver::gethostbyname {host args} {
    variable uid
    set token [namespace current]::[incr uid]
    upvar #0 $token state
    array set state [list state waiting host $host -command {} -timeout {}]
    while {[string match -* [set option [lindex $args 0]]]} {
        switch -exact -- $option {
            -command { set state(-command) [Pop args 1] }
            -timeout { set state(-timeout) [Pop args 1] }
            --       { Pop args ; break }
            default {
                set erropts [join [array names state -*] ", "]
                return -code error "invalid option \"$option\":\
                    must be one of $erropts"
            }
        }
        Pop args
    }

    if {$state(-timeout) ne {}} {
        set state(afterid) [after $state(-timeout)\
                                [list set $token\(state\) timeout]]
    }
    Queue $token
    return $token
}

proc ::resolver::status {token} {
    upvar #0 $token state
    return $state(state)
}

proc ::resolver::error {token} {
    upvar #0 $token state
    set result ""
    if {$state(state) eq "error" } {
        set result $state(result)
    } elseif {$state(state) eq "timeout"} {
        set result "name lookup for \"$state(host)\" timed out"
    }
    return $result
}

proc ::resolver::query {token} {
    upvar #0 $token state
    return $state(host)
}

proc ::resolver::result {token} {
    upvar #0 $token state
    if {$state(state) eq "ok"} {
        return $state(result)
    }
    return ""
}

proc ::resolver::wait {token} {
    upvar #0 $token state
    if {$state eq "waiting"} {
        ::vwait $token\(state\)
    }
}

proc ::resolver::cleanup {token} {
    upvar #0 $token state
    unset -nocomplain state
}

# -------------------------------------------------------------------------

proc ::resolver::Init {} {
    set cmd [auto_execok tcl_resolv]
    variable pipe [open |$cmd w+]
    puts stderr "opened new pipe '$pipe'"
    fconfigure $pipe -blocking 0 -buffering line -encoding utf-8
    fileevent $pipe readable [list [namespace origin Read] $pipe]
    variable queue
    if {[llength $queue] > 0} {
        fileevent $pipe writable [namespace code [list Write $pipe]]
    }
}

proc ::resolver::Queue {token} {
    variable pipe
    variable queue
    if {$pipe eq {}} { Init }
    lappend queue $token
    fileevent $pipe writable [list [namespace origin Write] $pipe]
}

proc ::resolver::Read {chan} {
    variable pipe
    variable waiting
    if {[gets $chan line] > 0} {
        #puts stderr "read: '$line'"
        set token [lindex $waiting 0]
        set waiting [lrange $waiting 1 end]
        if {$token ne {}} {
            upvar #0 $token state
            if {[info exists state(afterid)]} { after cancel $state(afterid) }
            set state(result) [string trim $line]
            set code ok
            if {[string match "error:*" $line]} { set code error }
            set state(state) $code
            if {$state(-command) ne {}} {
                if {[catch {uplevel \#0 $state(-command) [list $token]} err]} {
                    puts stderr "callback failed: $err"
                }
            }
        } else {
            puts stderr "error: got token '$token' for '$line'"
        }
    }
    if {[eof $chan]} {
        puts stderr "eof on $chan"
        fileevent $chan readable {}
        close $chan
        set pipe {}
    }
}

proc ::resolver::Write {host} {
    variable pipe
    variable queue
    variable waiting
    fileevent $pipe writable {}
    foreach token $queue {
        upvar #0 $token state
        set state(channel) $pipe
        puts $pipe $state(host)
        #puts stderr "wrote: $state(host)"
        lappend waiting $token
    }
    set queue {}
    flush $pipe
}

proc ::resolver::Shutdown {} {
    variable pipe
    if {[catch {close $pipe} msg]} {
        puts stderr "closing pipe: '$msg'" 
    }
    set pipe {}
}

proc ::resolver::Pop {varname {nth 0}} {
    upvar $varname args
    set r [lindex $args $nth]
    set args [lreplace $args $nth $nth]
    return $r
}

# -------------------------------------------------------------------------

package provide resolver $::resolver::version

# -------------------------------------------------------------------------