#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@" -name tknap

#
# TkNap v@TKNAP_VERSION@
#
# Copyright (C) 2000 Mats Peterson
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
#
# Please send any comments/bug reports to
# matsp888@yahoo.com  (Mats Peterson)
#

if {[info tclversion] < 8.3} {
    puts "You must have Tcl/Tk version 8.3 or higher to run this program."
    exit
}

set nap(debug) 0

set nap(version) @TKNAP_VERSION@
set nap(mptkversion) @MPTK_VERSION@
set nap(libversion) @NAPLIB_VERSION@

set exec_prefix @prefix@
set nap(pkgdir) @libdir@

if {[lsearch -exact $auto_path $nap(pkgdir)] == -1} {
    lappend auto_path $nap(pkgdir)
}

package require mpTk $nap(mptkversion)
package require -exact napLib $nap(libversion)

namespace import mpTk::Dir::choosedir
namespace import mpTk::Inp::get_input 
namespace import mpTk::Msg::msgbox mpTk::Msg::simplemsg
namespace import mpTk::Msg::errmsg mpTk::Msg::infomsg
namespace import mpTk::Info::showinfo mpTk::List::listscroll
namespace import mpTk::Progress::pbar mpTk::Misc::centerwin
namespace import mpTk::Net::waitsock mpTk::Net::waitvar
namespace import mpTk::Net::writesock
namespace import mpTk::Text::textscroll 
namespace import napLib::mp3info

source "[file dirname [info script]]/napchat.tcl"

#    Link types
#
#    0 = unknown
#    1 = 14.4
#    2 = 28.8
#    3 = 33.6
#    4 = 57.6
#    5 = 64k ISDN
#    6 = 128k ISDN
#    7 = Cable
#    8 = DSL
#    9 = T1
#   10 = T3 or higher


set nap(linkstr) {
    unknown
    14.4
    28.8
    33.6
    57.6
    {64k ISDN}
    {128k ISDN}
    Cable
    DSL
    T1
    T3+
}


proc napDebug {msg} {
    global nap
    
    if {$nap(debug)} {
	puts $nap(confd) $msg
	flush $nap(confd)
    }
}


proc napListMap {s} {
    return [string map {\{ \\\{ \\ \\\\} $s]
}


proc napGlobMap {s} {
    return [string map {* \\\*  ? \\\? [ \\\[ ] \\\]} $s]
}


proc napGetFileTail {fname} {
    if {[set i1 [string last \\ $fname]] != -1} {
	if {[set i2 [string last \\ [string range $fname \
		0 [expr {$i1 - 1}]]]] != -1} {
            set tail [expr {$i2 + 1}]
	} else {
	    set tail [expr {$i1 + 1}]
	}
    } else {
	set tail 0
    }
    return [string range $fname $tail end]
}


proc napAdjLen {mode data} {
    global nap

    switch $mode {
	clist {
	    set len [string length [lindex [split $data] 0]]
	    if {$len > $nap(cnamelen)} {
		set nap(cnamelen) $len
	    }
	}
	server {
	    set len [string length [lindex [napListMap $data] 0]]
	    if {$len > $nap(srvnamelen)} {
		set nap(srvnamelen) $len
	    }
	}
    }
}


proc napUlMark {active} {
    global nap

    if {$active} {
	set bgcolor #f0f0f0
    } else {
	set bgcolor $nap(oldbg)
    }

    .cmdline configure -bg $bgcolor

    foreach w $nap(cwinlist) {
	$w.out configure -bg $bgcolor
    }
    foreach w $nap(pwinlist) {
	$w.out configure -bg $bgcolor
    }
}


proc napConvertIp {ip} {
    set a1 [expr {$ip & 0xff}]
    set a2 [expr {($ip >> 8) & 0xff}]
    set a3 [expr {($ip >> 16) & 0xff}]
    set a4 [expr {($ip >> 24) & 0xff}]
    return "$a1.$a2.$a3.$a4"
}


proc napCheckKbps {fd} {
    global nap

    if {! [winfo exists $nap(dlwin,$fd)]} return

    set now [clock clicks -milliseconds]
    set sec [expr {($now-$nap(dlstart,$fd))/1000.0}]
    set kb [expr {($nap(dlcount,$fd) - $nap(dloffset,$fd))/1024.0}]
    set nap(dlkbps,$fd) "[format "%.2f" [expr {$kb/$sec}]] kB/sec"
	
    after 2000 [list napCheckKbps $fd]
}


proc napReadMP3Data {fd} {
    global nap
    
    if {([eof $fd]) || ([fconfigure $fd -error] != "")} {
        napEndDl $fd 1
        if {$nap(dlcount,$fd) < $nap(dltot,$fd)} {
	    set r [msgbox -icon info -font {Helvetica -13 bold} \
		-default yes -type yesno \
		-message "[file tail $nap(dlname,$fd)]: Premature EOF\nTry a resume?"]
	    if {$r == "yes"} {
		napDownload2 $nap(dlnick,$fd) $nap(dlfname,$fd) \
			$nap(dlname,$fd) $nap(dltot,$fd) resume \
			$nap(dlcount,$fd)
	    }
	}
	return
    }

    set data [read $fd]

    if {[string length $data]} {
	puts -nonewline $nap(mp3fd,$fd) $data
	set nap(dlcount,$fd) [expr {$nap(dlcount,$fd) + \
		[string length $data]}]
	set nap(dlpercent,$fd) \
		[expr {$nap(dlcount,$fd) * 100 / $nap(dltot,$fd)}]
	set nap(dlpercentd,$fd) "$nap(dlpercent,$fd)%"
	if {$nap(dlcount,$fd) == $nap(dltot,$fd)} {
	   napEndDl $fd 1
	}
    }
}


proc napWriteMP3Data {fd} {
    global nap

    if {[set r [fconfigure $fd -error]] != ""} {
	napEndUl $fd 1
	return
    }

    if {[eof $nap(mp3fd,$fd)]} {
	napEndUl $fd 1
	return
    }
    
    set data [read $nap(mp3fd,$fd) 1024]
  
    if {[string length $data]} {
	if {[catch {writesock $fd $data} r]} {
	    napEndUl $fd 1
	}
	set nap(ulcount,$fd) [expr {$nap(ulcount,$fd) + \
		[string length $data]}]
    }
    update idletasks
}


proc napGetMsg {} {
    global nap

    if {[eof $nap(srvsock)]} {
	if {$nap(auto_reconnect)} {
	    napReconnect
	} else {
	     napDisconnect "Server EOF"
	}
	return
    }

    if {[set r [fconfigure $nap(srvsock) -error]] != ""} {
	if {$nap(auto_reconnect)} {
	    napReconnect
	} else {
	    napDisconnect $r
	}
	return
    }

    set hdr [read $nap(srvsock) 4]
    if {[string length $hdr] < 4} {
	return
    }

    binary scan $hdr ss len type

    set data [read $nap(srvsock) $len]

    napDebug "$type: $data"

    switch $type {
	0 {				# error message
	    if {$nap(auto_reconnect)} {
	        after idle [list napShowMiscInfo $data normal]
	    } else {
	        after idle [list errmsg $data]
	    }
	}

	3 {				# login ack
	    set nap(server) $nap(srvaddr2)
	    after idle napDoLoginStuff
	}

	8 {				# registration success
	    set loginmsg "$nap(username) $nap(password)"
	    append loginmsg " $nap(dataport) \"TkNap v$nap(version)\""
	    append loginmsg " $nap(linktype) $nap(email)"
	    if {[catch {napSendMsg 6 $loginmsg} r]} {
		napDisconnect $r
		return
	    }
	    set nap(registered) 1
	}

	9 {				# nickname already registered
	    after idle [list napDisconnect "Nick name already registered"]
	}

	10 {				# invalid nickname
	    after idle [list napDisconnect "Invalid nick name"]
	}

	201 {				# search response
	    napAddResult $data 0
	}

	202 {				# end of search response
	    after idle {napShowResults normal}
	}

	204 {				# download ack
	    regexp {^(.+) (.+) (.+) "(.+)" (.+) (.+)$} $data \
		    match nick ip port fname md5 link
	    set nap(destip) [napConvertIp $ip]
	    set nap(destport) $port
	}

	205 {				# private message
	    after idle [list napRcvGlobal $type $data]
	}
                		
	206 {				# get error
	    after idle [list errmsg "Get $data failed"]
	}

	209 {                           # hotlist user signon
	    regexp {^([^ ]+) (.+)$} $data match nick link
	    lappend nap(online) $nick
	    set nap(online) [lsort -dictionary $nap(online)]
	    after idle [list napShowMiscInfo "$nick has signed on" normal]
	}

	210 {                           # hotlist user signoff
	    set nick $data
	    set n [lsearch -exact $nap(online) $nick]
	    if {$n != -1} {
		set nap(online) [lreplace $nap(online) $n $n]
	    }
	    after idle [list napShowMiscInfo "$nick has signed off" normal]
	}

	212 {				# browse response
	    napAddResult $data 1
	}

	213 {				# end of browse list
	    after idle {napShowResults browse}
	}

	214 {				# server stats
	    set tmp "Users: [lindex $data 0]"
	    append tmp "   Files: [lindex $data 1]"
	    set nap(servinfo) $tmp
	}

	301 {				# hotlist ack
	    set nap(hlack) $data
	}

	302 {                           # hotlist error
	    set nap(hlack) "Error"
	}

	401 {                           # channel part ack
	    napDoPartChannel $data
	}

	403 {				# public message to channel
	    napRcvChannel $type $data
	}

	404 {				# various info/errors
	    if {$data == "--"} return
	    after idle [list napShowMiscInfo $data normal]
	}

	405 {				# join acknowledge

	}

	406 {				# user joined channel
	    napUpdateUserList $type $data
	}
        
	407 {				# user left channel
	    napUpdateUserList $type $data
	}

	408 {				# user list entry
	    napUpdateUserList $type $data
	}
        
	409 {				# end of user list
	    napDoJoinChannel $data
	}
                
	410 {				# channel topic
	    napRcvChannel $type $data
	}
                    
	501 {				# alternate download ack
	    regexp {^(.+) (.+) (.+) "(.+)" (.+) (.+)$} $data \
		    match nick ip port fname md5 link
	    set ip [napConvertIp $ip]
	    after idle [list napFwUpload $nick $ip $port $fname]
	}
        
	604 {				# whois response
	    napShowWhois $data
	}

	605 {				# whowas response
	    napShowWhowas $data
	}
			
	607 {				# upload request
	    if {[catch {napSendMsg 608 $data} r]} {
		after idle [list napDisconnect $r]
	    }
	}

	613 {                           # set data port
	    after idle [list napSetDataPort $data 0]
	}

	615 {                           # end of ban lists
	    napDoShowBans
	}

	616 {				# banned ip entry
	    lappend nap(blist) $data
	}

	617 {				# end of channel list
	    napShowChannels
	}
        
	618 {				# channel list entry
	    napAdjLen clist $data
	    lappend nap(clist) $data
	}
        
	621 {				# message of the day
	    napShowMiscInfo $data motd
	}

	627 {				# operator message
	    after idle [list napRcvGlobal $type $data]
	}

	628 {				# global message
	    after idle [list napRcvGlobal $type $data]
	}

	629 {				# banned nickname entry

	}

	751 {				# ping
	    napRcvGlobal $type $data
	} 

	752 {				# pong
	    napRcvGlobal $type $data
	} 

	824 {				# emote
	    napRcvChannel $type $data
	} 

	10112 {				# server link entry
	    if {$data == ""} {
		napDoShowLinks
	    } else {
		lappend nap(llist) $data
	    }
	}

	10115 {				# server stats
	    napDoShowStats $data
	}

	10203 {                         # user info
	    napShowMiscInfo $data normal
	}

	default {

	}
    }
    update idletasks
}


proc napSendMsg {type s} {
    global nap 

    set tmp [binary format ss [string length $s] $type]
    append tmp $s

    if {[catch {writesock $nap(srvsock) $tmp} r]} {
	error $r
    }
}


proc napSearch2 {} {
    global nap

    set msg {}

    if {[string length [string trim $nap(srch,artist)]]} {
	append msg "FILENAME CONTAINS \"$nap(srch,artist)\""
    }

    append msg " MAX_RESULTS $nap(max_files)"

    if {[string length [string trim $nap(srch,title)]]} {
	append msg " FILENAME CONTAINS \"$nap(srch,title)\""
    }

    if {[string length [string trim $nap(srch,link)]]} {
	append msg " LINESPEED \"AT LEAST\" $nap(srch,link)"
    }

    if {[string length [string trim $nap(srch,bitrate)]]} {
	append msg " BITRATE \"AT LEAST\" \"$nap(srch,bitrate)\""
    }

    if {[string length [string trim $nap(srch,freq)]]} {
	append msg " FREQ \"AT LEAST\" \"$nap(srch,freq)\""
    }

    simplemsg create {Getting search results...}

    set nap(slist) {}
    set nap(fnlen) 0
    set nap(nicklen) 0
    if {[catch {napSendMsg 200 $msg} r]} {
	napDisconnect $r
    }
}


proc napSearch {} {
    global nap

    if {! $nap(conn)} return

    set nap(old_focus) [focus]

    set w .swin
    catch {destroy $w}

    toplevel $w -relief raised -bd 2
    wm title $w "Search"
    wm resizable $w false false

    frame $w.spacer -height 4m
    pack $w.spacer

    foreach i {artist title bitrate freq link} \
	    title {Artist Title {Bit Rate} Frequency Connection} \
	    width {40 40 3 5 9} {
	frame $w.$i
	pack $w.$i -padx 5m -pady 2m -fill x
	label $w.$i.label -anchor w -width 12 -text $title
	pack $w.$i.label -side left
	if {$i == "link"} {
	    tk_optionMenu $w.$i.linkmenu nap(srch,linkstr) \
		14.4 28.8 33.6 57.6 {64k ISDN} {128k ISDN} Cable DSL T1 T3+
	    pack $w.$i.linkmenu -side left
	} else {
	    entry $w.$i.entry -width $width -textvariable nap(srch,$i)
	    pack $w.$i.entry -side left
	    frame $w.$i.rmarg -width 1m
	    pack $w.$i.rmarg -side right
	}
    }

    proc napSearchExit {} {
	global nap
	set nap(srch,link) \
		[lsearch -exact $nap(linkstr) $nap(srch,linkstr)]
	catch {focus $nap(old_focus)}
	catch {destroy .swin}
    }

    frame $w.buttons
    pack $w.buttons -pady 4m
    button $w.buttons.search -default active -text Search \
	    -command {napSearchExit; napSearch2}
    button $w.buttons.cancel -text Cancel \
	    -command {catch {focus $nap(old_focus)}; destroy .swin}
    pack $w.buttons.search $w.buttons.cancel -side left -padx 4m

    centerwin $w

    bind $w <Escape> {catch {focus $nap(old_focus)}; destroy .swin}
    bind $w <Return> {napSearchExit; napSearch2}
    
    focus .swin.artist.entry
    .swin.artist.entry selection range 0 end
}


proc napBrowse2 {nick} {
    global nap
    
    simplemsg create {Getting browse list...}

    set nap(slist) {}
    set nap(fnlen) 0

    if {[catch {napSendMsg 211 $nick} r]} {
	napDisconnect $r
    }
}


proc napBrowse {} {
    global nap

    if {! $nap(conn)} return

    set s [get_input {} {Browse user}]
    if {$s == ""} return

    napBrowse2 $s
}


proc napAddResult {data mode} {
    global nap

    set mapdata [napListMap $data]
    set fname [lindex $mapdata $mode]
    set tail [napGetFileTail $fname]
	
    if {$nap(showpath)} {
	set len [string length $fname]
    } else {
	set len [string length $tail]
    }
    if {$len > $nap(fnlen)} {
	set nap(fnlen) $len
    }

    if {! $mode} {
	set len [string length [lindex $mapdata 6]]
	if {$len > $nap(nicklen)} {
	    set nap(nicklen) $len
	}
    }

    lappend nap(slist) "$mapdata [list $tail]"
}


proc napShowResults {mode} {
    global nap

    if {! [llength $nap(slist)]} {
	simplemsg destroy
	infomsg "No files found"
	return
    }

    set nap(slist) [lsort -dictionary -index end $nap(slist)]
    set nap(dlist) {}

    set fnfmt "[expr {$nap(fnlen)+6}]s"

    switch $mode {
        browse {
	    set titles {{File Name} Size {Bit Rate} Frequency Time}
	    foreach i $nap(slist) {
		if {$nap(showpath)} {
		    set fname [lindex $i 1]
		} else {
		    set fname [lindex $i end]
		}
		set nick [lindex $i 0]
		set size [lindex $i 3]
		set bitrate [lindex $i 4]
		set freq [lindex $i 5]
		set t [lindex $i 6]
		set time [format "%02d:%02d" [expr {$t / 60}] \
			[expr {$t % 60}]]
		set entry \
			[format "%-$fnfmt%-12s%-7s%-9s%-9s" \
			$fname $size $bitrate $freq $time]
		lappend nap(dlist) $entry
	    }
	    set title "Browse Results For $nick"
	}
	normal {
	    set nickfmt "[expr {$nap(nicklen)+4}]s"
	    set titles {{File name} Size {Bit rate} Frequency Time \
		    User Connection}
	    foreach i $nap(slist) {
		if {$nap(showpath)} {
		    set fname [lindex $i 0]
		} else {
		    set fname [lindex $i end]
		}
		set size [lindex $i 2]
		set bitrate [lindex $i 3]
		set freq [lindex $i 4]
		set t [lindex $i 5]
		set time [format "%02d:%02d" [expr {$t / 60}] \
			[expr {$t % 60}]]
		set nick [lindex $i 6]
		set link [lindex $i 8]
		set entry \
			[format "%-$fnfmt%-12s%-7s%-9s%-9s%-$nickfmt%s" \
			$fname $size $bitrate $freq $time $nick \
			"[lindex $nap(linkstr) $link]    "]
		lappend nap(dlist) $entry
	    }
	    set title "Search Results"
	}
    }

    simplemsg destroy
    
    set w .rwin
    set nap(old_focus) [focus]
    catch {destroy $w}
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w $nap(rwidth)x$nap(rheight)
    wm title $w $title

    frame $w.spacer -height 2m
    pack $w.spacer

    frame $w.titles
    pack $w.titles -padx 2m -fill x
    set n 0
    foreach i $titles {
	label $w.titles.title$n -text $i
	pack $w.titles.title$n -side left -expand yes
	incr n
    }

    frame $w.reslist
    pack $w.reslist -expand yes -fill both -padx 2m -pady 2m
    listscroll $w.reslist $nap(rwidth) 1 1
    $w.reslist.list configure -font $nap(monofont) -listvar nap(dlist)

    frame $w.buttons
    pack $w.buttons -pady 2m
    button $w.buttons.get -default active -text Get -takefocus 0 \
	    -command [list napDownload $mode]
    button $w.buttons.quit -text Quit -takefocus 0 \
	    -command {catch {focus $nap(old_focus)}; destroy .rwin}
    pack $w.buttons.get $w.buttons.quit \
	    -side left -padx 2m

    bind $w <Return> [list napDownload $mode]
    bind $w <q> {catch {focus $nap(old_focus)}; destroy .rwin}

    focus $w.reslist.list
    $w.reslist.list selection set 0 0
    $w.reslist.list activate 0
    update idletasks
}


proc napWhois {} {
    global nap

    if {! $nap(conn)} return

    set s [get_input {} {Whois}]
    if {$s == ""} return

    if {[catch {napSendMsg 603 $s} r]} {
	napDisconnect $r
    }
}


proc napShowWhois {data} {
    global nap

    regexp {^([^ ]+) "?([^ "]+)"? ([-0-9]+) "(.*)" "([^ ]+)" ([0-9]+) ([0-9]+)\
([0-9]+) ([0-9]+) "(.+)" *([0-9]*) *([0-9]*) *([^ ]*) *([0-9]*) *([0-9]*)\
*([^ ]*) *([^ ]*)$} \
            $data match nick level time channels status shared dl ul \
	    link client dltot ultot ip connport dataport email server 

    set titles {Nick Level Time Channels Status Shared Downloads Uploads \
	    Connection Client}

    set values [list $nick $level]

    set d [expr {$time / 86400}]
    set h [expr {($time / 3600) % 24}]
    set m [expr {($time / 60) % 60}]
    set s [expr {$time % 60}]

    set dtxt "day"
    if {$d > 1} {
	set dtxt "days"
    }
    set htxt "hr"
    if {$h > 1} {
	set htxt "hrs"
    }

    set ontime "$s sec"
    if {$m} {
	set ontime "$m min  $ontime"
    }
    if {$h} {
	set ontime "$h $htxt  $ontime"
    }
    if {$d} {
	set ontime "$d $dtxt  $ontime"
    }

    lappend values $ontime [string trim $channels]
    lappend values $status $shared $dl $ul
    lappend values [lindex $nap(linkstr) $link] $client
    set titlew 12
    set valuew 30
    set spacing 1.5m

    if {$email != ""} {
	lappend titles {Tot Downloads} {Tot Uploads} {IP Addr} \
		{Conn Port} {Data Port} {E-Mail}
	lappend values $dltot $ultot $ip $connport $dataport $email
	if {$server != ""} {
	    lappend titles {Server}
	    lappend values $server
	}
	set titlew 16
	set spacing 0.25m
    }
    
    showinfo .wwin {Whois Results} $titles $values $titlew $valuew $spacing
}


proc napShowWhowas {data} {
    set titles {Nick Level {Last seen}}
    regexp {^(.+) (.+) (.+)$} [string map {\" ""} $data] \
	    match nick level last
    set values [list $nick $level [clock format $last]]
    showinfo .wwin {Whowas Results} $titles $values 12 0
}


proc napShowMiscInfo {text mode} {
    global nap

    set w .iwin

    if {! [winfo exists $w]} {
	toplevel $w -relief raised -bd 2 -class Tknap
	wm title $w "Info"
	wm geometry $w 80x12

	frame $w.info
	pack $w.info -padx 2m -pady 2m -expand yes -fill both
	textscroll $w.info $nap(rwidth) 1 1
	$w.info.text configure -wrap word -font $nap(monofont)
        button $w.clear -text Clear \
		-command [list $w.info.text delete 0.0 end]
	pack $w.clear -pady 2m
    }

    switch $mode {
	init {
	    return
	}
	motd {
	    napAddText $w.info.text "$text"
	}
	normal {
	    napAddText $w.info.text "\n$text"
	}
    }

    update idletasks
}


proc napShowBans {} {
    global nap

    set nap(blist) {}

    if {[catch {napSendMsg 615 {}} r]} {
	napDisconnect $r
	return
    }
}


proc napDoShowBans {} {
    global nap

    if {! [llength $nap(blist)]} {
	infomsg "No bans found"
	return
    }

    set w .bwin
    set nap(old_focus) [focus]
    catch {destroy $w}
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w 80x20
    wm title $w "Current Bans"

    frame $w.spacer -height 2m
    pack $w.spacer

    frame $w.titles
    pack $w.titles -padx 2m -fill x
    set n 0
    foreach i {Target {Set By} Reason Time} {
	label $w.titles.title$n -text $i
	pack $w.titles.title$n -side left -expand yes
	incr n
    }

    frame $w.bans
    pack $w.bans -padx 2m -pady 2m -expand yes -fill both
    listscroll $w.bans 80 1 1
    $w.bans.list configure -font $nap(monofont)
    $w.bans.xscroll configure -takefocus 0
    $w.bans.yscroll configure -takefocus 0

    frame $w.buttons
    pack $w.buttons -pady 2m
    button $w.buttons.delete -width 5 -text Delete -takefocus 0 \
            -command {napRemoveBan [focus]}
    button $w.buttons.quit -width 5 -text Quit -takefocus 0 \
            -command {catch {focus $nap(old_focus)}; destroy .bwin}
    pack $w.buttons.delete $w.buttons.quit -side left -padx 2m

    proc napRemoveBan {w} {
	global nap

	set n [$w curselection]
	if {$n == ""} return
	if {! [regexp {^[^ ]+} \
		[lindex $nap(blist) $n] ban]} return
	set nap(blist) [lreplace $nap(blist) $n $n]
	$w delete $n $n
	if {[catch {napSendMsg 614 $ban} r]} {
	    napDisconnect $r
	}
    }

    bind $w.bans.list <Delete> {napRemoveBan %W}
    bind $w.bans.list <Button> {focus %W}

    bind $w <q> {catch {focus $nap(old_focus)}; destroy .bwin}

    set nap(blist) [lsort -dictionary $nap(blist)]

    foreach i $nap(blist) {
	regexp {^([^ ]+) ([^ ]+) "(.*)" ([0-9]+) 0$} $i match \
		target setby reason time
	set entry [format "%-34.32s%-34.32s%-60.58s%-35s" \
		$target $setby $reason [clock format $time]]
	$w.bans.list insert end $entry
    } 

    focus $w.bans.list
    $w.bans.list selection set 0 0
    $w.bans.list activate 0
    update idletasks
}


proc napShowLinks {} {
    global nap

    set nap(llist) {}

    if {[catch {napSendMsg 10112 {}} r]} {
	napDisconnect $r
	return
    }
}


proc napDoShowLinks {} {
    global nap

    if {! [llength $nap(llist)]} {
	infomsg "No links exist"
	return
    }

    set w .lwin
    set nap(old_focus) [focus]
    catch {destroy $w}
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w 70x18
    wm title $w "Server Links"

    frame $w.spacer -height 2m
    pack $w.spacer

    frame $w.titles
    pack $w.titles -padx 2m -fill x
    set n 0
    foreach i {{Server} Hops {Connected Via}} {
	label $w.titles.title$n -text $i
	pack $w.titles.title$n -side left -expand yes
	incr n
    }

    frame $w.links
    pack $w.links -padx 2m -pady 2m  -expand yes -fill both
    listscroll $w.links 70 1 1
    $w.links.list configure -font $nap(monofont)
    $w.links.yscroll configure -takefocus 0

    button $w.ok -width 4 -text OK -default active \
	    -command {catch {focus $nap(old_focus)}; destroy .lwin}
    pack $w.ok -pady 2m

    bind $w <Return> {catch {focus $nap(old_focus)}; destroy .lwin}

    foreach i $nap(llist) {
	set i [napListMap $i]
	set dest "[lindex $i 2]:[lindex $i 3]"
	set hops [lindex $i 4]
	if {$hops} {
	    set via "[lindex $i 0]:[lindex $i 1]"
	} else {
	    set via "DIRECT"
	}
	set entry [format "%-35.34s%-6s%-35.34s" $dest $hops $via]
	$w.links.list insert end $entry
    } 

    focus $w.links.list
    $w.links.list selection set 0 0
    $w.links.list activate 0
}


proc napShowStats {} {
    global nap

    if {[catch {napSendMsg 10115 {}} r]} {
	napDisconnect $r
	return
    }
}


proc napDoShowStats {stats} {
    global nap

    set titles {{Connected Clients} {Connected Servers} {Total Clients}}
    lappend titles Files Gigabytes {Active Channels} {Start Time}
    lappend titles Uptime {Memory Used} {Registered Users}
    regexp {^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)\
([^ ]+) ([^ ]+) ([^ ]+)} \
	    $stats match clients servers users files kbytes \
	    channels start upsec memory regusers

    set d [expr {$upsec / 86400}]
    set h [expr {($upsec / 3600) % 24}]
    set m [expr {($upsec / 60) % 60}]

    set dtxt "day"
    if {$d > 1} {
	set dtxt "days"
    }
    set htxt "hr"
    if {$h > 1} {
	set htxt "hrs"
    }

    set uptime "$m min"
    if {$h} {
	set uptime "$h $htxt  $uptime"
    }
    if {$d} {
	set uptime "$d $dtxt  $uptime"
    }

    set gbytes [format "%.1f" [expr [format "%f" $kbytes] / 1048576.0]]

    set values [list $clients $servers $users $files $gbytes \
	    $channels [clock format $start] $uptime $memory $regusers]
    showinfo .stwin {Server Statistics} $titles $values 19 0
}


proc napShowHotlist {} {
    global nap

    if {! [llength $nap(hotlist)]} {
	errmsg "No users in hotlist"
	return
    }

    set w .hlwin
    set nap(old_focus) [focus]
    catch {destroy $w}
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w 35x15
    wm title $w "Hotlist"

    frame $w.hotlist
    pack $w.hotlist -padx 2m -pady 2m  -expand yes -fill both
    listscroll $w.hotlist 40 1 1
    $w.hotlist.list configure -font $nap(monofont) -listvar nap(hotlist)
    $w.hotlist.yscroll configure -takefocus 0

    proc napRemoveHotEntry {} {
	global nap
	set n [.hlwin.hotlist.list curselection]
	if {$n == ""} return
	if {[catch {napHotlist - [lindex $nap(hotlist) $n] 1} r]} {
	    napDisconnect $r
	}
	update idletasks
    }

    frame $w.buttons
    pack $w.buttons -pady 2m
    button $w.buttons.delete -width 5 -text Delete \
	    -command napRemoveHotEntry
    button $w.buttons.quit -width 5 -text Quit \
	    -command {catch {focus $nap(old_focus)}; destroy .hlwin}
    pack $w.buttons.delete $w.buttons.quit -side left -padx 2m

    bind $w <q> {catch {focus $nap(old_focus)}; destroy .hlwin}
    bind $w.hotlist.list <Delete> napRemoveHotEntry

    focus $w.hotlist.list
    $w.hotlist.list selection set 0 0
    $w.hotlist.list activate 0
    update idletasks
}


proc napShowOnline {} {
    global nap

    set w .olwin
    if {[winfo exists $w]} return

    set nap(old_focus) [focus]
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w 35x15
    wm title $w "Hotlist Users Online"

    frame $w.online
    pack $w.online -padx 2m -pady 2m  -expand yes -fill both
    listscroll $w.online 40 1 1
    $w.online.list configure -font $nap(monofont) -listvar nap(online)
    $w.online.yscroll configure -takefocus 0

    proc napBrowseOnline {} {
	global nap
    	set n [.olwin.online.list curselection]
	set e [lindex $nap(online) $n]
	napBrowse2 $e
    }

    frame $w.buttons
    pack $w.buttons -pady 2m

    button $w.buttons.browse -width 5 -text Browse -default active \
	    -command napBrowseOnline
    button $w.buttons.quit -width 5 -text Quit \
	    -command {catch {focus $nap(old_focus)}; destroy .olwin}
    pack $w.buttons.browse $w.buttons.quit -side left -padx 2m

    bind $w <Return> napBrowseOnline
    bind $w <q> {catch {focus $nap(old_focus)}; destroy .olwin}

    focus $w.online.list
    $w.online.list selection set 0 0
    $w.online.list activate 0
    update idletasks
}


proc napShowServers {} {
    global nap

    set nap(srvnamelen) 0
    foreach i $nap(servers) {
	if {! [regexp {^(.+):(.+):(.+)$} $i match addr port meta]} {
	    errmsg "Invalid server entry"
	    return
	}
	napAdjLen server "$addr $port"
    } 

    if {$nap(srvnamelen) < 21} {
	set nap(srvnamelen) 21
    }    
    set srvfmt "[expr {$nap(srvnamelen) + 7}]s"

    set w .srvwin
    set nap(old_focus) [focus]
    catch {destroy $w}
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w 50x15
    wm title $w "Server List"

    frame $w.spacer -height 2m
    pack $w.spacer
    set titles {Address Port {Server Type}}
    frame $w.titles
    pack $w.titles -padx 2m -fill x
    set n 0
    foreach i $titles {
	label $w.titles.title$n -text $i
	pack $w.titles.title$n -side left -expand yes
	incr n
    }

    frame $w.servers
    pack $w.servers -padx 2m -pady 2m  -expand yes -fill both
    listscroll $w.servers 50 1 1
    $w.servers.list configure -font $nap(monofont)
    $w.servers.yscroll configure -takefocus 0

    proc napConnToListServer {} {
	global nap

	set n [.srvwin.servers.list curselection]
	set e [lindex $nap(servers) $n]
	regexp {^(.+):(.+):(.+)$} $e match addr port meta
	catch {focus $nap(old_focus)}
	destroy .srvwin
	update idletasks
	napDisconnect
	napConnect $addr $port $meta 1
    }

    frame $w.buttons
    pack $w.buttons -pady 2m
    button $w.buttons.connect -width 5 -text Conn -default active \
	    -command napConnToListServer
    button $w.buttons.quit -width 5 -text Quit \
	    -command {catch {focus $nap(old_focus)}; destroy .srvwin}
    pack $w.buttons.connect $w.buttons.quit -side left -padx 2m

    bind $w <Return> napConnToListServer
    bind $w <q> {catch {focus $nap(old_focus)}; destroy .srvwin}

    set typestr {Normal Meta}
    foreach i $nap(servers) {
	regexp {^(.+):(.+):(.+)$} $i match addr port meta
	set e [format "%-$srvfmt%-11s%-10s" \
		$addr $port [lindex $typestr $meta]]
	$w.servers.list insert end $e
    } 

    focus $w.servers.list
    $w.servers.list selection set 0 0
    $w.servers.list activate 0
    update idletasks
}


proc napUlStats {} {
    global nap

    set nap(ulnames) [array names nap {ullist *}]
    if {! [llength $nap(ulnames)]} {
	infomsg "No active uploads"
	return
    }

    set fnlen 0
    foreach i $nap(ulnames) {
	set fname [lindex $nap($i) 1]
	if {$nap(showpath)} {
	    set len [string length $fname]
	} else {
	    set len [string length [napGetFileTail $fname]]
	}
	if {$len > $fnlen} {
	    set fnlen $len
	}
    }

    set fnfmt "[expr {$fnlen+6}]s"

    proc napUpdUlStats {w fnfmt} {
	global nap
	if {! [winfo exists $w]} return
	set n [$w.ullist.list curselection]
	set nap(uldlist) {}
	foreach i $nap(ulnames) {
	    if {! [info exists nap($i)]} {
		lappend nap(uldlist) "*** Transfer complete ***"
		continue
	    }
	    set nick [lindex $nap($i) 0]
	    set fname [lindex $nap($i) 1]
	    if {! $nap(showpath)} {
		set fname [napGetFileTail $fname]
	    }
	    set bytes $nap(ulcount,[lindex $i 1])
	    set entry [format "%-$fnfmt%-20s%-13s" $fname $nick $bytes]
	    lappend nap(uldlist) $entry
	}
	set nap(uldlist) [lsort -dictionary $nap(uldlist)]
	if {$n == ""} {
	    set n 0
	}
	$w.ullist.list selection set $n $n
	$w.ullist.list activate $n
	after 2000 [list napUpdUlStats $w $fnfmt]
    }

    set w .uswin
    set nap(old_focus) [focus]
    catch {destroy $w}
    toplevel $w -relief raised -bd 2 -class Tknap
    wm geometry $w $nap(rwidth)x$nap(rheight)
    wm title $w "Active Uploads"

    frame $w.spacer -height 2m
    pack $w.spacer

    frame $w.titles
    pack $w.titles -padx 2m -fill x
    set n 0
    foreach i {Nick {File name} {Bytes transferred}} {
	label $w.titles.title$n -text $i
	pack $w.titles.title$n -side left -expand yes
	incr n
    }

    frame $w.ullist
    pack $w.ullist -expand yes -fill both -padx 2m -pady 2m
    listscroll $w.ullist $nap(rwidth) 1 1
    $w.ullist.list configure -font $nap(monofont) -listvar nap(uldlist)

    proc napUlAbort {w} {
	global nap
	set n [$w.ullist.list curselection]
	if {$n == ""} return
	set i [lindex $nap(ulnames) $n]
	if {! [info exists nap($i)]} return
	napEndUl [lindex $i 1] 1
	$w.ullist.list delete $n $n
	update idletasks
    }

    frame $w.buttons
    pack $w.buttons -pady 2m
    button $w.buttons.abort -width 4 -text Abort -takefocus 0 \
	    -command [list napUlAbort $w]
    button $w.buttons.quit -width 4 -text Quit -takefocus 0 \
	    -command {catch {focus $nap(old_focus)}; destroy .uswin}
    pack $w.buttons.abort $w.buttons.quit \
	    -side left -padx 2m

    bind $w <a> [list napUlAbort $w]
    bind $w <q> {catch {focus $nap(old_focus)}; destroy .uswin}

    focus $w.ullist.list
    update idletasks
    after 0 [list napUpdUlStats $w $fnfmt]
}


proc napDownload2 {nick fname dlname fsize mode offset} {
    global nap

    if {[catch {set fd [open $dlname a]} r]} {
	errmsg $r
	return
    } else {
	close $fd
    }

    simplemsg create {Preparing for download...}

    if {[catch {napSendMsg 203 "$nick \"$fname\""} r]} {
	napDisconnect $r
	return
    }

    set nap(destport) {}
    waitvar nap(destport) $nap(timeout)

    if {$nap(destport) == "Timeout"} {
	simplemsg destroy
	errmsg "Timeout"
	return
    }

    set fwdl 0

    if {$nap(destport) == 0} {
	#
	# Firewalled download
	#
        
	set fwdl 1
        
	# alternate download request
	set nap(fwaddr) $nap(destip)
	if {[catch {napSendMsg 500 "$nick \"$fname\""} r]} {
	    set nap(fwaddr) {}
	    napDisconnect $r
	    return
	}

	# Wait for connect from peer
	waitvar nap(fwaddr) $nap(timeout)
	if {$nap(fwaddr) == "Timeout"} {
	    simplemsg destroy
	    errmsg "Timeout"
	    return
	}

	set fd $nap(fwfd)

	if {[catch {waitsock $fd writable $nap(timeout)} r]} {
	    napAbortDl $fd 0 $r
	    return
	}
	if {[catch {writesock $fd "1"} r]} {
	    napAbortDl $fd 0 $r
	    return
	}

	if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	    napAbortDl $fd 0 $r
	    return
	}
	set gettxt [read $fd 4]
        
	if {$gettxt != "SEND"} {
	    napAbortDl $fd 0 "EOF from peer or invalid data"
	    return
	}

	fconfigure $fd -blocking no

	if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	    napAbortDl $fd 0 $r
	    return
	}
	set finfo [read $fd]

	if {! [regexp {^([^ ]+) (".+") ([0-9]+)$} $finfo match \
		nick fname fsize]} {
	    napAbortDl $fd 0 "EOF from peer or invalid data"
	    return
	}

	if {[catch {waitsock $fd writable $nap(timeout)} r]} {
	    napAbortDl $fd 0 $r
	    return
	}
	#Send byte offset
	if {[catch {writesock $fd $offset} r]} {
	    napAbortDl $fd 0 $r
	    return
	}
    } else {
	#
	# Normal download
	#

	if {[catch {set fd [socket -async $nap(destip) $nap(destport)]} r]} {
	    simplemsg destroy
	    errmsg $r
	    napUnreachable $nick
	    return
	}

	fconfigure $fd -blocking no -translation binary

	if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	    napAbortDl $fd 0 $r
	    napUnreachable $nick
	    return
	}

	if {[read $fd 1] != "1"} {
	    napAbortDl $fd 0 "EOF from peer or invalid data"
	    return
	}

	if {[catch {writesock $fd "GET"} r]} {
	    napAbortDl $fd 0 $r
	    return
	}

	if {[catch {writesock $fd "$nap(username) \"$fname\" $offset"} r]} {
	    napAbortDl $fd 0 $r
	    return
	}

	set rsize {}

	while {1} {
	    if {[catch {waitsock $fd readable $nap(timeout)} r]} {
		napAbortDl $fd 0 $r
		return
	    } else {
		set c [read $fd 1]
	    }

	    if {! [regexp {[0-9]} $c]} break
        
	    append rsize $c
	}

	if {($rsize != $fsize) || ($rsize == 0)} {
	    napAbortDl $fd 0 \
		    "EOF from peer, file not shared or invalid data"
	    return
	}
    }
    
    if {[catch {napSendMsg 218 {}} r]} {
	napAbortDl $fd 0 $r
	napDisconnect
	return
    }

    set nap(dlfname,$fd) $fname
    set nap(dlname,$fd) $dlname
    if {[catch {set nap(mp3fd,$fd) [open $nap(dlname,$fd) a]} r]} {
	napAbortDl $fd 1 $r
	return
    }
    fconfigure $nap(mp3fd,$fd) -translation binary
    seek $nap(mp3fd,$fd) $offset start

    if {! $fwdl} {
	puts -nonewline $nap(mp3fd,$fd) $c
    } 

    simplemsg destroy
    
    set w .dlwin_$fd
    toplevel $w -relief raised -bd 2
    wm resizable $w false false
    wm title $w "Download  $nick"
    frame $w.spacer -width 80m -height 4m
    pack $w.spacer
    label $w.fname -wraplength 70m -anchor w -justify left \
	    -text [file tail $nap(dlname,$fd)]
    label $w.size -text "Size: $fsize"
    pack $w.fname $w.size -padx 5m -pady 2m

    frame $w.progress
    set f $w.progress
    pack $f -fill x
    frame $f.spacer1 -width 6m
    pbar $f.pbar nap(dlpercent,$fd)
    $f.pbar configure -width 50
    label $f.percent -anchor w -textvariable nap(dlpercentd,$fd)
    label $f.kbps -anchor e -textvariable nap(dlkbps,$fd)
    frame $f.spacer2 -width 5m
    pack $f.spacer1 -side left
    pack $f.pbar $f.percent -side left -padx 1m -pady 3m
    pack $f.spacer2 -side right
    pack $f.kbps -side right -padx 1m -pady 3m

    button $w.abort -text Abort -command [list napAbortDl $fd 1]
    pack $w.abort -pady 4m

    set nap(dlwin,$fd) $w
    set nap(dlnick,$fd) $nick
    set nap(dltot,$fd) $fsize
    set nap(dlcount,$fd) $offset
    set nap(dloffset,$fd) $offset
    if {! $fwdl} {
	incr nap(dlcount,$fd)
    }
    set nap(dlpercent,$fd) \
	[expr {$nap(dlcount,$fd) * 100 / $nap(dltot,$fd)}]
    set nap(dlpercentd,$fd) "$nap(dlpercent,$fd)%"
    set nap(dlstart,$fd) [clock clicks -milliseconds]

    fileevent $fd readable [list napReadMP3Data $fd]
    after 2000 [list napCheckKbps $fd]
}


proc napDownload {mode} {
    global nap

    if {$nap(dl)} return
    set nap(dl) 1

    set n [.rwin.reslist.list curselection]
    if {$n == ""} {
	set nap(dl) 0
	return
    }
    set e [lindex $nap(slist) $n]
    
    switch $mode {
	browse {
	    set nick [lindex $e 0]
	    set fname [lindex $e 1]
	    set fsize [lindex $e 3]
	}
	normal {
	    set fname [lindex $e 0]
	    set fsize [lindex $e 2]
	    set nick [lindex $e 6]
	}
    }    

    if {$fsize == 0} {
	errmsg "File size is 0 bytes"
	set nap(dl) 0
	return
    }

    set types {
	{{MP3 Files}  {.mp3 .Mp3 .MP3}}
    }
    regexp {.+\\(.+)} $fname match sfname
    set savename [tk_getSaveFile -filetypes $types \
	    -initialdir $nap(dldir) -initialfile $sfname -parent .rwin]
        if {$savename == ""} {
	set nap(dl) 0
	return
    }
    set nap(dldir) [file dirname $savename]

    set offset 0
    if {[file exists $savename] && ([file tail $savename] == $sfname)} {
	set storedsize [file size $savename]
	if {$storedsize == $fsize} {
	    infomsg "File is complete"
	    set nap(dl) 0
	    return
	}
	set offset $storedsize
    }

    napDownload2 $nick $fname $savename $fsize $mode $offset
    set nap(dl) 0
}


proc napEndDl {fd dlstat} {
    global nap

    catch {close $fd}
    catch {close $nap(mp3fd,$fd)}

    if {$dlstat} {
	catch {destroy $nap(dlwin,$fd)}
	if {[catch {napSendMsg 219 {}} r]} {
	    napDisconnect $r
	    return
        }
    }
}


proc napAbortDl {fd dlstat {msg ""}} {
    global nap

    simplemsg destroy
    napEndDl $fd $dlstat
    catch {file delete $nap(dlname,$fd)}
    if {$msg != ""} {
	errmsg $msg
    }
}


proc napUnreachable {nick} {
    global nap

    if {[catch {napSendMsg 626 $nick} r]} {
	napDisconnect $r
    }
}


proc napUpload {fd addr port} {
    global nap naplist

    fconfigure $fd -translation binary
    
    # If firewalled download, just get the necessary
    # info and return
    if {($addr == $nap(fwaddr)) || \
          (($addr == "127.0.0.1") && ($nap(fwaddr) != ""))} {
	set nap(fwfd) $fd
	set nap(fwaddr) {}
	return
    }

#   puts "Incoming upload request from $addr:$port"
 
    if {[catch {waitsock $fd writable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }
    
    if {[catch {writesock $fd "1"} r]} {
	napEndUl $fd 0
	return
    }

    if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }

    set gettxt [read $fd 3]
        
    if {$gettxt != "GET"} {
	napEndUl $fd 0
	return
    }

    fconfigure $fd -blocking no

    if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }
    set finfo [read $fd]

    if {! [regexp {^([^ ]+) "(.+)" ([0-9]+)$} $finfo match \
	    nick fname offset]} {
	napEndUl $fd 0
	return
    }

    set nap(ullist\ $fd) [list $nick $fname]

    fconfigure $fd -blocking yes

    regexp {^C:(.+)$} $fname match fname
    set fname [string map {\\ /} $fname]

    if {! [info exists naplist($fname)]} {
	catch {writesock $fd "FILE NOT SHARED"}
	napEndUl $fd 0
	return
    }

    if {[catch {set nap(mp3fd,$fd) [open $fname r]} r]} {
	napEndUl $fd 0
	return
    }
    fconfigure $nap(mp3fd,$fd) -translation binary
    seek $nap(mp3fd,$fd) $offset start

    if {[catch {waitsock $fd writable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }
  
    set fsize [file size $fname]
    if {[catch {writesock $fd $fsize} r]} {
	napEndUl $fd 0
	return
    }

    if {[catch {napSendMsg 220 {}} r]} {
	napEndUl $fd 0
	napDisconnect $r
	return
    }

    napUlMark 1
    set nap(ulcount,$fd) 0
    fileevent $fd writable [list napWriteMP3Data $fd]    
}


proc napFwUpload {nick ip port fname} {
    global nap naplist

    if {[catch {set fd [socket -async $ip $port]} r]} {
	return
    }

    fconfigure $fd -translation binary

    if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }
    if {[read $fd 1] != "1"} {
	napEndUl $fd 0
	return
    }

    set nap(ullist\ $fd) [list $nick $fname]

    regexp {^C:(.+)$} $fname match realname
    set realname [string map {\\ /} $realname]

    if {! [info exists naplist($realname)]} {
	napEndUl $fd 0
	return
    }

    if {! [file exists $realname]} {
	napEndUl $fd 0
	return
    }
    set fsize [file size $realname]
    
    if {[catch {waitsock $fd writable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }
    if {[catch {writesock $fd "SEND"} r]} {
	napEndUl $fd 0
	return
    }
    if {[catch {writesock $fd "$nap(username) \"$fname\" $fsize"} r]} {
	napEndUl $fd 0
	return
    }

    fconfigure $fd -blocking no
    
    if {[catch {waitsock $fd readable $nap(timeout)} r]} {
	napEndUl $fd 0
	return
    }
    set offset [read $fd]

    if {! [regexp {^[0-9]+$} $offset]} {
	napEndUl $fd 0
	return
    }

    if {[catch {set nap(mp3fd,$fd) [open $realname r]} r]} {
	napEndUl $fd 0
	return
    }
    fconfigure $nap(mp3fd,$fd) -translation binary
    seek $nap(mp3fd,$fd) $offset start

    if {[catch {napSendMsg 220 {}} r]} {
	napEndUl $fd 0
	napDisconnect $r
	return
    }

    napUlMark 1
    set nap(ulcount,$fd) 0
    fconfigure $fd -blocking yes
    fileevent $fd writable [list napWriteMP3Data $fd]    
}


proc napEndUl {fd ulstat} {
    global nap
    
    catch {close $nap(mp3fd,$fd)}
    catch {close $fd}
    catch {unset nap(ullist\ $fd)}

    if {! [llength [array names nap {ullist *}]]} {
	napUlMark 0
    }

    if {$ulstat} {
	if {[catch {napSendMsg 221 {}} r]} {
	    napDisconnect $r
	}
    }
}


proc napHotlist {mode nick manual} {
    global nap

    if {$mode == "+"} {
	set n 0
	foreach lnick $nap(hotlist) {
	    if {[string equal -nocase $lnick $nick]} {
		break
	    }
	    incr n
	}
	if {$manual && ($n < [llength $nap(hotlist)])} {
	    errmsg "User $nick is already in your hotlist"
	    return
	}
	if {$nap(conn)} {
	    set nap(hlack) {}
	    if {[catch {napSendMsg 207 $nick} r]} {
		error $r
	    }
	    waitvar nap(hlack) $nap(timeout)
	    if {$nap(hlack) == "Timeout"} {
		error "Timeout"
	    } 
	    if {$nap(hlack) == "Error"} {
		errmsg "Error adding $nick to hotlist"
		return
	    }
	}
	if {$manual} {
	    lappend nap(hotlist) $nick
	}
    } else {
	set n 0
	foreach lnick $nap(hotlist) {
	    if {[string equal -nocase $lnick $nick]} {
		break
	    }
	    incr n
	}
	if {$n == [llength $nap(hotlist)]} {
	    errmsg "User $nick is not in your hotlist"
	    return
	}
	if {$nap(conn)} {
	    if {[catch {napSendMsg 303 $nick} r]} {
		error $r
	    }
	}
	set nap(hotlist) [lreplace $nap(hotlist) $n $n]
	if {$nap(conn)} {
	    set n 0
	    foreach i $nap(online) {
		if {[string equal -nocase $i $nick]} {
		    set nap(online) [lreplace $nap(online) $n $n]
		    break
		}
		incr n
	    }
	}
    }
}


proc napSendHotlist {} {
    global nap

    if {! [llength $nap(hotlist)]} return
    foreach i $nap(hotlist) {
	if {[catch {napHotlist + $i 0} r]} {
	    error $r
	}
    }
}


proc napReadHotlist {} {
    global nap
    
    set listfile ~/.tknaphot
    if {[file exists $listfile]} {
	set fd [open $listfile r]
	while {[gets $fd line] >= 0} {
	    lappend nap(hotlist) $line
	}
	close $fd
    }
}


proc napSaveHotlist {} {
    global nap

    set listfile ~/.tknaphot
    if {! [llength $nap(hotlist)]} {
	catch {file delete $listfile}
	return
    }
    set fd [open $listfile w]
    foreach i $nap(hotlist) {
	puts $fd $i
    }
    close $fd
}


proc napNotifySharedFile {e} {
    global nap

    regexp {^/(.+)$} [lindex [napListMap $e] 0] match fname
    set fname "C:\\[string map {/ \\} $fname]"
    set entry "\"$fname\" [lrange $e 1 end]"

    if {[catch {napSendMsg 100 $entry} r]} {
	error $r
    }
}


proc napNotifySharedFiles {} {
    global nap naplist

    if {! [array size naplist]} return
        
    simplemsg create "Please wait, notifying shared files..."

    foreach f [array names naplist] {
	if {[catch {napNotifySharedFile $naplist($f)} r]} {
	    error $r
	}
    }

    simplemsg destroy
}


proc napRemoveSharedFile {e} {
    global nap

    regexp {^/(.+)$} [lindex [napListMap $e] 0] match fname
    set fname "C:\\[string map {/ \\} $fname]"
    if {[catch {napSendMsg 102 $fname} r]} {
	error $r
    }
}


proc napRecurse {dir} {
    global nap naplist

    set findCmd [list |find $dir ( -type f -o -type l ) \
	    -name {*.[Mm][Pp]3} 2>/dev/null]
    if {[catch {set input [open $findCmd r]} r]} {
	errmsg $r
	return
    }
 
    while {[gets $input f] != -1} {
	# Only regular files
	if {! [file isfile $f]} continue

 	# Skip file names with double quotes in them
	if {[string match {*"*} $f]} continue

	if {([info exists naplist($f)]) &&
		([file size $f] == [lindex [napListMap $naplist($f)] 2])} {
	    continue
	}

	if {[catch {set info [mp3info $f]} r]} {
	    napShowMiscInfo $r normal
	    continue
	}
	set size [lindex $info 0]
	set bitrate [lindex $info 1]
	set freq [lindex $info 2]
	set sec [lindex $info 3]

	set naplist($f) "\"$f\" $nap(md5) $size $bitrate $freq $sec"
	if {$nap(conn)} {
	    if {[catch {napNotifySharedFile $naplist($f)} r]} {
		error $r
	    }
	}
    }

    catch {close $input}
}


proc napBuildFileList {} {
    global nap naplist

    set listfile ~/.tknaplist
    
    if {[file exists $listfile]} {
	set fd [open $listfile r]
	while {[gets $fd line] >= 0} {
	    set naplist([lindex [napListMap $line] 0]) $line
	}
	close $fd
	return	  
    }
    
    if {! [llength $nap(uldirs)]} return
    simplemsg create "Please wait, building file list..."
    foreach i $nap(uldirs) {
	napRecurse $i
    }
    simplemsg destroy
}


proc napSaveFileList {} {
    global nap naplist

    if {! [array size naplist]} {
	catch {file delete ~/.tknaplist}
	return
    }
    set fd [open ~/.tknaplist w]
    foreach f [array names naplist] {
	puts $fd $naplist($f)
    }
    close $fd
}


proc napUlDirError {r} {
    global nap naplist

    simplemsg destroy
    napDisconnect $r
    errmsg "File list processing error. Rebuilding from scratch."
    catch {array unset naplist}
    file delete ~/.tknaplist
    napBuildFileList
}


proc napUlDirs {mode {dir ""}} {
    global nap naplist

    if {$mode == "show"} {
	set titles {}
	set values {}
	foreach i $nap(uldirs) {
	    lappend titles $i
	    lappend values {}
	}
	if {! [llength $titles]} {
	    infomsg "No shared directories found"
	    return
	}
	showinfo .ulwin {Current Shared Directories} \
		$titles $values 50 0 1
	return
    }

    if {$dir != ""} {
	if {! [file isdirectory $dir]} {
	    errmsg "Invalid directory"
	    return
	}
    } else {
	set dir [choosedir -initialdir $nap(initdir) \
		-title "[string totitle $mode] Shared Directory"]
	update idletasks
	if {$dir == ""} return
    }

    if {$mode == "add"} {
	set newdir 1
	set samedir 0
	foreach i $nap(uldirs) {
	    if {[string match "[napGlobMap $i]*" $dir]} {
		set newdir 0
		if {[string equal $i $dir]} {
		    set samedir 1
		}
		break
	    } elseif {[string match "[napGlobMap $dir]*" $i]} {
		set newdir -1
		break
	    }
	}
	if {$newdir == -1 } {
	    errmsg "Please remove the children directories first"
	    return
	} elseif {$newdir} {
	    lappend nap(uldirs) $dir
	} elseif {! $samedir} {
	    errmsg "A shared parent directory already exists"
	    return
	}
	simplemsg create "Please wait, updating file list..."
	if {! $newdir} {
	    foreach f [array names naplist] {
		if {! [file exists $f]} {
		    if {$nap(conn)} {
			if {[catch {napRemoveSharedFile $naplist($f)} r]} {
			    napUlDirError $r
			    return
			}
		    }
		    unset naplist($f)
		}
	    }
	}
	if {[catch {napRecurse $dir} r]} {
	    napUlDirError $r
	    return
	}
	simplemsg destroy
    } else {
	if {[set i [lsearch $nap(uldirs) $dir]] == -1} {
	    errmsg "Directory is not shared"
	    return
	}
	set nap(uldirs) [lreplace $nap(uldirs) $i $i]
	foreach f [array names naplist] {
	    if {[string match "[napGlobMap $dir]*" $f]} {
		if {$nap(conn)} {
		    if {[catch {napRemoveSharedFile $naplist($f)} r]} {
			napUlDirError $r
			return
		    }
		}
		unset naplist($f)
	    }
	}
    }
    set nap(initdir) $dir
}


proc napExecRCfile {} {
    global nap env

    set nap(rcfile) ~/.tknaprc

    if {! [file exists $nap(rcfile)]} {
	puts stderr "ERROR: missing .tknaprc file"
	exit
    }
    set fd [open $nap(rcfile) r]
    set nap(initcmds) {}
    while {[gets $fd line] >= 0} {
	lappend nap(initcmds) $line
    }
    close $fd

    set names {registered username password email uldirs}
    set n 0

    for {set i 0} {($i < [llength $nap(initcmds)]) && \
	    ($n < [llength $names])} {incr i} {
	set line [lindex $nap(initcmds) $i]
	foreach name $names {
	    if {[regexp "set +nap\\($name\\)" $line]} {
		set nap(rcline,$name) $i
		incr n
		break
	    }
	}
    }

    eval [join $nap(initcmds) "\n"]

    set nap(old_registered) $nap(registered)
    set nap(old_username) $nap(username)
    set nap(old_password) $nap(password)
    set nap(old_email) $nap(email)
    set nap(old_uldirs) $nap(uldirs)
}


proc napUpdateRCfile {} {
    global nap

    set names {registered username password email uldirs}
    set changed 0
    foreach i $names {
	if {$nap($i) != $nap(old_$i)} {
	    set n $nap(rcline,$i)
	    set nap(initcmds) [lreplace $nap(initcmds) $n $n \
		    [list set nap($i) $nap($i)]]
	    incr changed
	    if {$changed == [llength $names]} break
	}
    }
    if {! $changed} return

    set fd [open $nap(rcfile) w]
    foreach i $nap(initcmds) {
	puts $fd $i
    }
    close $fd
}


proc napProcessCmd {c s} {
    global nap
    
    set s [string trim $s]

    if {! [regexp {^/([^ ]+) *(.*)$} $s match cmd data]} {
        errmsg "Invalid or ambiguous command"
        return
    }

    switch -regexp -- $cmd {
	{^(ba|ban)$} {
	    if {! $nap(conn)} return
	    if {! [regexp {^([^ ]+) *(.*)$} $data match user reason]} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 612 "$user \"$reason\""} r]} {
		napDisconnect $r
	    }
	}

	{^(br|bro|brow|brows|browse)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    napBrowse2 $data
	}

	{^(chl|chle|chlev|chleve|chlevel)$} {
	    if {! $nap(conn)} return
	    if {$c == ""} {
		errmsg "Not available from here"
		return
	    }
	    if {[catch {napSendMsg 10201 "$c $data"} r]} {
		napDisconnect $r
	    }
	}

	{^(cle|clea|clear)$} {
	    if {! $nap(conn)} return
	    if {$c == ""} {
		errmsg "Not available from here"
		return
	    }
	    if {[catch {napSendMsg 820 "$c \"$data\""} r]} {
		napDisconnect $r
	    }
	}

	{^(clo|cloa|cloak)$} {
	    if {! $nap(conn)} return
	    if {[catch {napSendMsg 652 {}} r]} {
		napDisconnect $r
	    }
	}

	{^(con|conn)$} {
	    napConnect $nap(srvaddr) $nap(srvport) $nap(meta) 1
	}

	{^(da|dat|data|datap|datapo|datapor|dataport)$} {
	    if {! [regexp {^([^ ]+) *(.*)$} $data match p1 p2]} {
		errmsg "Missing parameters"
		return
	    }
	    if {$p2 == ""} {
		if {! [regexp {^[0-9]+$} $p1]} {
		    errmsg "Invalid port number"
		    return
		}
		napSetDataPort $p1 1
	    } else {
		if {! $nap(conn)} return
		if {[catch {napSendMsg 613 "$p1 $p2"} r]} {
		    napDisconnect $r
		}
	    }
	}

	{^(de|deo|deop)$} {
	    if {! $nap(conn)} return
	    if {$c == ""} {
		errmsg "Not available from here"
		return
	    }
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 10205 "$c $data"} r]} {
		napDisconnect $r
	    }
	}

	{^(di|dis|disc)$} {
	    if {! $nap(conn)} return
	    napDisconnect
	}

	{^(e|em|ema|emai|email)$} {
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    set nap(email) $data
	    if {$nap(conn)} {
		if {[catch {napSendMsg 702 $data} r]} {
		    napDisconnect $r
		}
	    }
	}

	{^(g|gm|gms|gmsg)$} {
	    if {! $nap(conn)} return
	    napSendGlobal 628 $data
	}

	{^(j|jo|joi|join)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    napJoinChannel $data
	}

	{^(kic|kick)$} {
	    if {! $nap(conn)} return
	    if {$c == ""} {
		errmsg "Not available from here"
		return
	    }
	    if {! [regexp {^([^ ]+) *(.*)$} $data match user reason]} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 10202 "$c $user \"$reason\""} r]} {
		napDisconnect $r
	    }
	}

	{^(kil|kill)$} {
	    if {! $nap(conn)} return
	    if {! [regexp {^([^ ]+) *(.*)$} $data match user reason]} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 610 "$user \"$reason\""} r]} {
		napDisconnect $r
	    }
	}

	{^(kills|killse|killser|killserv)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} { 
		errmsg "Missing parameters"
		return
	    }
	    if {[catch {napSendMsg 10110 $data} r]} {
		napDisconnect $r
	    }
	}
	
	{^(le|lev|leve|level)$} {
	    if {! $nap(conn)} return
	    if {! [regexp {^([^ ]+) +(.+)$} $data match user level]} {
		errmsg "Missing parameters"
		return
	    }
	    if {[catch {napSendMsg 606 "$user $level"} r]} {
		napDisconnect $r
	    }
	}

	{^(lin|link)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameters"
		return
	    }
	    if {! [regexp {^([^ ]+):([0-9]+) *(.*)$} \
		    $data match addr port remote]} {
		errmsg "Invalid format"
		return
	    }
	    if {[catch {napSendMsg 10100 "$addr $port $remote"} r]} {
		napDisconnect $r
	    }
	}

	{^links$} {
	    if {! $nap(conn)} return
	    napShowLinks
	}

	{^(lis|list)$} {
	    if {! $nap(conn)} return
	    napListChannels
	}

	{^(listb|listba|listban|listbans)$} {
	    if {! $nap(conn)} return
	    napShowBans
	}

	{^me$} {
	    if {! $nap(conn)} return
	    if {$c != ""} {
		napSendChannel $c 824 $data
	    } else {
		errmsg "Not available from here"
	    }
	}

	{^(ms|msg)$} {
	    if {! $nap(conn)} return
	    napSendGlobal 205 $data
	}

	{^(mo|mot|motd)$} {
	    if {! $nap(conn)} return
	    napShowMiscInfo {} normal
	    if {[catch {napSendMsg 621 {}} r]} {
		napDisconnect $r
	    }
	}

	{^(mu|muz|muzz|muzzl|muzzle)$} {
	    if {! $nap(conn)} return
	    if {! [regexp {^([^ ]+) *(.*)$} $data match user reason]} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 622 "$user \"$reason\""} r]} {
		napDisconnect $r
	    }
	}

	{^(no|not|noti|notif|notify)$} {
	    if {$data == ""} {
		napShowHotlist
		return
	    }
	    if {! [regexp {^([\+-])([^ ]+)$} $data match mode nick]} {
		errmsg "Invalid format"
		return
	    }
	    if {[catch {napHotlist $mode $nick 1} r]} {
		napDisconnect $r
	    } 
        }

	{^(on|onl|onli|onlin|online)$} {
	    if {! $nap(conn)} return
	    if {! [llength $nap(online)]} {
		errmsg "No users online"
		return
	    }
	    napShowOnline
	}

	{^op$} {
	    if {! $nap(conn)} return
	    if {$c == ""} {
		errmsg "Not available from here"
		return
	    }
	    if {[catch {napSendMsg 10204 "$c $data"} r]} {
		napDisconnect $r
	    }
	}

	{^(opm|opms|opmsg)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameters"
		return
	    }
	    if {[catch {napSendMsg 627 $data} r]} {
		napDisconnect $r
	    }
	}

	{^(par|part)$} {
	    if {! $nap(conn)} return
	    if {$c != ""} {
		napPartChannel $c
	    } else {
		errmsg "Not available from here"
	    }
	}

	{^(pas|pass|passw|passwo|passwor|password)$} {
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    set nap(password) $data
	    if {$nap(conn)} {
		if {[catch {napSendMsg 701 $data} r]} {
		    napDisconnect $r
		}
	    }
	}

	{^(pi|pin|ping)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    set nap(pstart,$data) [clock clicks -milliseconds]
	    napSendGlobal 751 $data
	}

	{^(qi|qin|qinf|qinfo)$} {
	    if {! $nap(conn)} return
	    if {$c == ""} {
		errmsg "Not available from here"
		return
	    }
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    napQuickInfo $c $data
	}

	{^(qu|qui|quit)$} {
	    napQuit
	}

	{^(re|rem|rems|remse|remser|remserv)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameters"
		return
	    }
	    if {[catch {napSendMsg 10111 $data} r]} {
		napDisconnect $r
	    }
	}

	{^(sea|sear|searc|search)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		napSearch
	    } else {
		set nap(srch,artist) $data
		set nap(srch,title) {}
		napSearch2
	    }
	}

	{^(ser|serv|serve|server)$} {
	    if {$data == ""} {
		napShowServers
		return
	    }	
	    if {! [regexp {^([^ ]+):([0-9]+):([01]) *$} \
		    $data match addr port meta]} {
		errmsg "Invalid parameters"
		return
	    }
	    napDisconnect
	    napConnect $addr $port $meta 1
	}

	{^(sh|sho|show|showp|showpa|showpat|showpath)$} {
	    if {$data == ""} {
		set state {disabled enabled}
		infomsg "Show full paths is [lindex $state $nap(showpath)]"
		return
	    }
	    if {! [regexp {^[01]$} $data]} {
		errmsg "Invalid parameter"
		return
	    }
	    set nap(showpath) $data
	}

	{^(sp|spe|spee|speed)$} {
	    if {! $nap(conn)} return
	    if {! [regexp {^([^ ]+) *(.*)$} $data match p1 p2]} {
		errmsg "Missing parameters"
		return
	    }
	    if {$p2 == ""} {
		if {! [regexp {^[0-9]+$} $p1]} {
		    errmsg "Invalid speed"
		    return
		}
		if {[catch {napSendMsg 700 $p1} r]} {
		    napDisconnect $r
		}
	    } else {
		if {[catch {napSendMsg 625 "$p1 $p2"} r]} {
		    napDisconnect $r
		}
	    }
	}

	{^(st|sta|stat|stats)$} {
	    if {! $nap(conn)} return
	    napShowStats
	}

	{^(to|top|topi|topic)$} {
	    if {! $nap(conn)} return
	    if {! [regexp {^([^ ]+) +(.+)$} $data match chn topic]} {
	    errmsg "Missing parameters"
		return
	    }
	    if {[catch {napSendMsg 410 "$chn $topic"} r]} {
		napDisconnect $r
	    }
	}

	{^(ula|ulad|uladd)$} {
	    napUlDirs add $data
	}

	{^(uld|uldi|uldir|uldirs)$} {
	    napUlDirs show
	}

	{^(ulr|ulre|ulrem)$} {
	    napUlDirs remove $data
	}

	{^(uls|ulst|ulsta|ulstat|ulstats)$} {
	    napUlStats
	}

	{^(unb|unba|unban)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 614 $data} r]} {
		napDisconnect $r
	    }
	}

	{^(unl|unli|unlin|unlink)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameters"
		return
	    }
	    if {[catch {napSendMsg 10101 $data} r]} {
		napDisconnect $r
	    }
	}

	{^(unm|unmu|unmuz|unmuzz|unmuzzl|unmuzzle)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 623 $data} r]} {
		napDisconnect $r
	    }
	}

	{^unr|unre|unreg$} {
	    set nap(registered) 0
	}

	{^(userm|usermo|usermod|usermode)$} {
	    if {! $nap(conn)} return
	    if {[catch {napSendMsg 10203 $data} r]} {
		napDisconnect $r
	    }
	}

	{^(usern|userna|usernam|username)$} {
	    if {$nap(conn)} {
		errmsg "Only when disconnected"
		return 
	    }
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    set nap(username) $data
        }

	{^(ver|vers|versi|versio|version)$} {
	    if {! $nap(conn)} return
	    if {[catch {napSendMsg 801 {}} r]} {
		napDisconnect $r
	    }
	}

	{^(w|wh|who|whoi|whois)$} {
	    if {! $nap(conn)} return
	    if {$data == ""} {
		errmsg "Missing parameter"
		return
	    }
	    if {[catch {napSendMsg 603 $data} r]} {
		napDisconnect $r
	    }
	}
        
	default {
	    errmsg "Invalid or ambiguous command"
	}
    }
}


proc napRegister {} {
    global nap

    set results {Success {User already registered} {Invalid username}}

    set nap(reg) {}
    waitvar nap(reg) $nap(timeout)
    if {$nap(reg) == "Timeout"} {
	return $nap(reg)
    } else {
	return [lindex $results $nap(reg)]
    }
}


proc napRedirect {addr port} {
    global nap

    if {[catch {set nap(srvsock) [socket -async $addr $port]} r]} {
	errmsg $r
	return
    }

    simplemsg create "Trying metaserver $addr..."

    catch {waitsock $nap(srvsock) readable $nap(timeout)} r

    simplemsg destroy

    if {$r != ""} {
	napDisconnect $r
	return
    }

    if {[catch {gets $nap(srvsock) tmp} r]} {
	napDisconnect $r
	return
    }
    catch {close $srv}

    regexp {[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+} $tmp servstr
    return $servstr
}


proc napDoLoginStuff {} {
    global nap
    
    if {[catch napNotifySharedFiles r]} {
	napDisconnect $r
	return
    }
    if {[catch napSendHotlist r]} {
	napDisconnect $r
	return
    }
    foreach i $nap(auto_commands) {
	napProcessCmd {} "/$i"
    }
}


proc napConnect {addr port meta first} {
    global nap

    if {$nap(conn)} return

    if {$nap(reconnid) != ""} {
        after cancel $nap(reconnid)
	set nap(reconnid) {}
    }

    set nap(srvaddr) $addr
    set nap(srvport) $port

    if {$meta} {
	set nap(meta) 1
	if {[set servstr [napRedirect $addr $port]] == ""} {
	    return
	}
	set tmp [split $servstr :]
	set nap(srvaddr2) [lindex $tmp 0]
	set nap(srvport2) [lindex $tmp 1]
    } else {
	set nap(meta) 0
	set nap(srvaddr2) $nap(srvaddr)
	set nap(srvport2) $nap(srvport)
    }

    if {[catch {set nap(srvsock) \
	    [socket -async $nap(srvaddr2) $nap(srvport2)]} r]} {
	errmsg $r
	return
    }

    fconfigure $nap(srvsock) -translation binary

    set msg "Connecting to $nap(srvaddr)..."
    if {$first} {
	simplemsg create $msg
    } else {
        napShowMiscInfo $msg normal
    }

    if {[catch {waitsock $nap(srvsock) writable $nap(timeout)} r]} {
	napDisconnect $r
	return
    }

    fileevent $nap(srvsock) writable {}
    fileevent $nap(srvsock) readable napGetMsg

    if {! $nap(registered)} {
	if {[catch {napSendMsg 7 $nap(username)} r]} {
	    napDisconnect $r
	    return
	}
    } else {
	set loginmsg "$nap(username) $nap(password)"
	append loginmsg " $nap(dataport) \"TkNap v$nap(version)\""
	append loginmsg " $nap(linktype)"

	if {[catch {napSendMsg 2 $loginmsg} r]} {
	    napDisconnect $r
	    return
	}
    }

    simplemsg destroy
    .icons.images.connect configure -image connect
    .icons.labels.connect configure -text Disc
    set nap(conn) 1
}


proc napReconnect {} {
    global nap

    napDisconnect
    napShowMiscInfo "Waiting..." normal
    set nap(reconnid) [after $nap(reconn_timeout) \
	    [list napConnect $nap(srvaddr) $nap(srvport) $nap(meta) 0]]
}

	    
proc napDisconnect {{msg ""}} {
    global nap

    simplemsg destroy
    catch {close $nap(srvsock)}
    .icons.images.connect configure -image disconnect
    .icons.labels.connect configure -text Connect
    set nap(conn) 0
    set nap(server) {}
    set nap(servinfo) {}
    set nap(fwaddr) {}
    set nap(motd) {}
    set nap(online) {}
    catch {array unset nap {pstart,*}}

    if {$msg != ""} {
	errmsg $msg
    }

    foreach i $nap(cwinlist) {
	catch {destroy $i}
    }
    foreach i $nap(pwinlist) {
	catch {destroy $i}
    }
    set nap(cwinlist) {}
    set nap(pwinlist) {}

    catch {destroy .rwin}
    catch {destroy .bwin}
    catch {destroy .lwin}
    catch {destroy .uswin}
    catch {destroy .clwin}
    catch {destroy .hlwin}
    catch {destroy .olwin}
}


proc napConnDisc {} {
    global nap

    if {! $nap(conn)} {
	napConnect $nap(srvaddr) $nap(srvport) $nap(meta) 1
    } else {
	napDisconnect
    }
}


proc napSetDataPort {port manual} {
    global nap

    napDebug "Setting data port to $port"
    catch {close $nap(datasock)}

    if {$port != 0} {
	if {[catch {set nap(datasock) \
		[socket -server napUpload $port]} r]} {
	    if {$manual} {
		errmsg $r
	    }
	    set nap(dataport) 0
	    if {$nap(conn)} {
		if {[catch {napSendMsg 703 0} r]} {
		    napDisconnect $r
		}
	    }
	    return
	}
    }

    set nap(dataport) $port
    if {$manual && $nap(conn)} {
	if {[catch {napSendMsg 703 $nap(dataport)} r]} {
	    napDisconnect $r
	}
    }
}


proc napInit {} {
    global nap env naplist

    set nap(conn) 0
    
    if {$nap(debug)} {
	set nap(confd) [open /dev/console w]
    }

    if {$nap(dataport) != 0} {
	napSetDataPort $nap(dataport) 1
    }

    if {! [info exists nap(servers)]} {
	set msg "Can't find the variable 'nap(servers)', "
	append msg "probably due to an outdated .tknaprc file. "
	append msg "Please use a copy from the current distribution "
	append msg "and re-edit it."
	errmsg $msg
	exit
    }
    set e [lindex $nap(servers) 0]
    if {! [regexp {^(.+):(.+):(.+)$} $e match \
	    nap(srvaddr) nap(srvport) nap(meta)]} {
	errmsg "Invalid server entry"
    }

    set nap(dl) 0
    set nap(server) {}
    set nap(servinfo) {}
    set nap(fwaddr) {}
    set nap(srch,artist) {}
    set nap(srch,title) {}
    set nap(srch,bitrate) $nap(min_bitrate)
    set nap(srch,freq) $nap(min_freq)
    set nap(srch,link) $nap(min_link)
    set nap(srch,linkstr) [lindex $nap(linkstr) $nap(srch,link)]
    set nap(md5) [string repeat "0" 32]
    set nap(cwinlist) {}
    set nap(pwinlist) {}
    set nap(curwin) {}
    set nap(motd) {}
    set nap(hotlist) {}
    set nap(online) {}
    set nap(firsthot) 1
    set nap(initdir) $env(HOME)
    array set naplist {}
    set nap(reconnid) {}
    napBuildFileList
    napReadHotlist
}


proc napQuit {} {
    global nap

    napUpdateRCfile
    napSaveHotlist
    napSaveFileList
    catch {close $nap(srvsock)}
    if {$nap(debug)} {
	catch {close $nap(confd)}
    }
    exit
}


#######################################################################


napExecRCfile

napShowMiscInfo {} init
wm geometry .iwin $nap(info_pos)

wm title . "TkNap v$nap(version)" 
wm resizable . false false

option add *Entry.font $nap(propfont)

set imgdir [file join $nap(pkgdir) napLib$nap(libversion) images]
foreach i {connect disconnect search browse whois quit} {
    image create photo $i -width 32 -height 32 -file $imgdir/$i.gif
}

frame .icons -relief raised -bd 2
pack .icons -fill x

frame .icons.spacer1 -height 1m
pack .icons.spacer1

set f .icons.images
frame $f
pack $f -fill x
foreach i {connect search browse whois quit} {
    if {$i == "connect"} {
	set img disconnect
	set cmd napConnDisc
    } else {
	set img $i
	set cmd nap[string totitle $i]
    }
    button $f.$i -image $img -width 42 -height 42 -bd 0 -command $cmd
    pack $f.$i -side left -padx 14 -pady 1m -expand yes
}

set f .icons.labels
frame $f
pack $f -fill x
foreach i {connect search browse whois quit} {
    label $f.$i -text [string totitle $i]  -bd 0 \
	    -highlightthickness 0 -width 7 -font $nap(labelfont)
    pack $f.$i -side left -pady 1m -expand yes
}

frame .icons.spacer2 -height 1m
pack .icons.spacer2

set f .info
frame $f -takefocus 0 
pack $f -fill x
label $f.server -font variable -textvariable nap(server)
label $f.servinfo  -font variable -textvariable nap(servinfo)
pack $f.server -side left -padx 5m -pady 6m
pack $f.servinfo -side right -padx 5m -pady 6m

entry .cmdline
pack .cmdline -fill x -padx 2m
frame .spacer -height 2m
pack .spacer

bind . <Control-c> {
    napConnect $nap(srvaddr) $nap(srvport) $nap(meta) 1
}
bind . <Control-d> napDisconnect
bind . <Control-s> napSearch
bind . <Control-b> napBrowse
bind . <Control-w> napWhois
bind . <Control-q> napQuit

bind .cmdline <Return> {
    set s [.cmdline get]
    .cmdline delete 0 end
    update idletasks
    napProcessCmd {} $s
}

wm geometry . $nap(main_pos)

set nap(oldbg) [lindex [.cmdline configure -background] 4]

focus .cmdline
update idletasks

napInit
.
