#
# $Source: /home/nlfm/Working/Zircon/Released/plugins/Dcc/RCS/dcc.tcl,v $
# $Date: 2001/07/10 15:39:59 $
# $Revision: 1.18.1.65 $
#
package provide Dcc 1.18
#
proc doDCC {net cmd nk} {
    switch -glob -- $nk {} - {[#&+]*} return
    $net configure -lastuser $nk
    [User :: make $net $nk] dcc $cmd
}
#
proc deSpace {file} {
    regsub -all { } $file {_} file
    return $file
}
#
proc dodRename {net file new} {
   switch {} $new return
   filerename $file $new
   uplevel #0 set DCCR($net,$file) 1
}
#
proc doGetDCC {net wh usr addr port leng posn args} {
    switch -- $args {} { return 0 }
    if {[catch {dectonet $addr} host]} { return 0 }
    switch $wh Chat {
	if {[catch {$net connect $host $port} sok]} {
	    $net display {} "*** Cannot connect to host $host ($sok)"
	    return 0
	}
	fconfigure $sok -translation {lf lf}
	upvar #0 $sok chdata
	set chdata(who) $usr
	$usr ref
	[set chdata(obj) \
	    [set this [$net eval "Chat [list [$usr name]] -caller $usr"]]] show
	$this addUser $chdata(who) 0 0
	$this configure -sock $sok
	fileevent $sok readable "dccChat r $sok"
    } default {
	if {[file exists [set file [lindex $args 0]]]} {
	    if {![file writable $file]} {
		tellError {} {File error} "Cannot write file $file."
		return 0
	    }
	    switch $posn [set fp [file size $file]] {} default {
	        switch [tk_dialog .@dg$net {DCC Get} \
	          "File \"$file\" already exists. Select the action you want to take." \
	          warning 0 Overwrite Resume {Rename incoming} \
	          {Rename Existing} Cancel] { 
	        0 {}
	        1 {
		      $net CTCP DCC [$usr name] "RESUME [deSpace [file tail $file]] $port $fp"
		      global Resume
		      if {![info exists Resume($usr)]} {$usr ref}
		      lappend Resume($usr) [list $file $addr $port $leng $fp]
		      return 0
		  }
	        2 {
		      global DCCR
		      set DCCR({$net,$file}) {}
		      tkwait window [mkEntryBox {} {Rename incoming} \
		        "Enter new name for incoming $file:" \
		        [list [list filename $file]] \
		        [list ok "set DCCR([list $net,$file])"] \
		        [list cancel {}]]
		      set v $DCCR({$net,$file})
		      unset DCCR({$net,$file})
		      switch {} $v { return 0 }
		      set file $v
		  }
	        3 {
		      global DCCR
		      set DCCR($net,$file) 0
		      tkwait window [mkEntryBox {} {Rename} \
		        "Enter new name for $file:" \
		        [list [list filename $file]] \
		        [list ok "dodRename $net [list $file]"] [list cancel {}]]
		      set v $DCCR($net,$file);
		      unset DCCR($net,$file)
		      if {!$v} {return 0}
		  }
	        4 {return 0}
	        }
	    }
	}
	set file [file join [file dirname $file] [file tail $file]]
	set sk [[$net sockcmd] -async $host $port]
	fconfigure $sk -blocking 1
	fileevent $sk writable "startGet $sk $net [list $file] $usr $leng $posn"
    }
    return 1
}
#
proc dccPick {net win id} {
    global DCCList
    set ix [lsearch $DCCList($net) "$id *"]
    foreach {dm op usr addr port fln leng posn} [lindex $DCCList($net) $ix] break
    switch $op {
    Send {handleSend $net $fln $usr $addr $port $leng $posn 0}
    default { doGetDCC $net $op $usr $addr $port $leng $posn $fln }
    }
    dccDel $net $win $id $usr
}
#
proc dccDel {net w id usr} {
    global DCCList
    notIdle {}
    listdel DCCList($net) [lsearch $DCCList($net) "$id *"]
    $usr deref
    if {[winfo exists $w]} {
	destroy $w.t$id $w.u$id $w.f$id $w.a$id $w.c$id
	if {[llength [winfo children $w]] <= 1} {
	    set z [winfo parent $w]
	    closeFrame [winfo parent $z] [winfo name $z] $net
	}
	switch {} $DCCList($net) {destroy [winfo toplevel $w]}
    }
    if {[winfo exists .@dls$net]} { buildDCCList $net }
}
#
proc addDCCRequest {net op usr fln addr port leng} {
    global DCCList
    if {![winfo exists [set w .@drq$net]]} {
	set DCCList($net) {}
	makeToplevel $w "[$net name] - Incoming DCC Offers" { } {}
	addSeparator [switchFrame $w sChat {Chat Offers} 0] \
	  -columnspan 5
	addSeparator [switchFrame $w sSend {File Send Offers} 0] \
	  -columnspan 5
	grid columnconfigure $w.sChat.bdy 1 -weight 1
	grid columnconfigure $w.sChat.bdy 2 -weight 1
	grid columnconfigure $w.sSend.bdy 1 -weight 1
	grid columnconfigure $w.sSend.bdy 2 -weight 1
    } {
	if {[set dl [$net dcclimit]] && [llength $DCCList($net)] > $dl} return
    }
    set win $w.s$op.bdy
    set rw [lindex [grid size $win] 1]
    set ix [newName dcc]
    grid [label $win.t$ix -text [getDate]] -row $rw -column 0 -sticky w
    grid [label $win.u$ix -text [$usr name]] -row $rw -column 1 -sticky w -padx 5
    grid [label $win.f$ix -text $fln] -row $rw -column 2 -sticky w -padx 5
    grid [button $win.a$ix -text [trans accept] -command "dccPick $net $win $ix"]\
      -sticky ew -row $rw -column 3
    grid [button $win.c$ix -text [trans cancel] -command "dccDel $net $win $ix $usr"]\
      -sticky ew -row $rw -column 4
    popup $w
    openFrame $w s$op $net
    lappend DCCList($net) [list $ix $op $usr $addr $port $fln $leng 0 0]
    $usr ref
    if {[winfo exists .@dls$net]} { buildDCCList $net }
}
#
proc handleDCC {net usr param prefix} {
    set pars [split $param]
    set fln [lindex $pars 1]
    set addr [lindex $pars 2]
    set port [lindex $pars 3]
    switch -exact -- [string toupper [lindex $pars 0]] {
    RESUME { handleResume $net $usr $fln $addr $port }
    ACCEPT { handleAccept $net $usr $fln $addr $port }
    SEND {
	    switch -glob -- $fln {.*} {set fln _[string range $fln 1 end]}
	    set leng [lindex $pars 4]
	    foreach x [$net autoget] {
		if {[regexp -- $x $prefix]} {
		    handleSend $net $fln $usr $addr $port $leng 0 1
		    return
		}
	    }
	    addDCCRequest $net Send $usr $fln $addr $port $leng
	}  
    CHAT {
	    switch WBOARD [string toupper $fln] {
		$net inform "Zircon does not implement DCC WBOARD"
		return
	    }
	    foreach x [$net autochat] {
		if {[regexp -- $x [string range $prefix 1 end]]} {
		    doGetDCC $net Chat $usr $addr $port {} 0 {}
		    return
		}
	    }
	    addDCCRequest $net Chat $usr {} $addr $port {}
	}
    }
}
#
proc dectonet {dec} {
    if {[string length $dec] == 10 && [set first [string index $dec 0]] > 1} {
	switch -- $first {
	    2 {set overflow "0 148 53 119"}
	    3 {set overflow "0 94 208 178"}
	    4 {set overflow "0 40 107 238"}
	}
	set dec [string range $dec 1 end]
    } else {
	set overflow {0 0 0 0}
    }   

    scan [format "%08x" $dec] "%2x%2x%2x%2x" net(3) net(2) net(1) net(0)

    for {set part 0; set carry 0} {$part < 4} {incr part} {
	set sum [expr {$net($part) + [lindex $overflow $part] + $carry}]
	set internet($part) [expr {$sum % 256}]
	set carry [expr {$sum / 256}]
    }

    return "$internet(3).$internet(2).$internet(1).$internet(0)"
}
#
switch -glob [info tclversion] 8.* {
proc killDel {arr usr file} {
    upvar #0 $arr gs
    set i 0
    if {![info exists gs($usr)]} return
    foreach p $gs($usr) {
	switch -- [lindex $p 1] $file {
	    listdel gs($usr) $i
	    switch {} $gs($usr) {unset gs($usr)}
	    set conn [lindex $p 2]
	    catch {fileevent $conn readable {}}
	    catch {close $conn}
	    return
	}
	incr i
    }
}
} default {
proc killDel {arr usr file} {
    upvar #0 $arr gs
    set i 0
    if {![info exists gs($usr)]} return
    foreach p $gs($usr) {
	switch -- [lindex $p 1] $file {
	    catch {exec kill -9 [pid [lindex $p 0]]} msg
	    listdel gs($usr) $i
	    switch {} $gs($usr) {unset gs($usr)}
	    set conn [lindex $p 2]
	    catch {fileevent $conn readable {}}
	    catch {close $conn}
	    return
	}
	incr i
    }
}
}
#
proc dccClose {net op usr file ix w} {
    set who [$usr name]
    switch -glob -- $op {
    Call {$usr unChat }
    Chat {catch {[Chat :: find $who $net] leave}}
    Get -
    Send -
    Offer {
	    upvar #0 $op$net av
	    foreach f $av($usr) {
		switch -- $file [lindex $f 1] {
		    killDel $op$net $usr $file
		    break
		}
	    }
	    destroy $w.f$ix
	}
    }
    destroy $w.l$ix $w.b$ix
    buildDCCList $net
}
#
proc doTitle {grd w sub net} {
    if {$grd > 1} { openFrame $w $sub $net} { closeFrame $w $sub $net}
}
#
proc buildDCCList {net args} {
    global tls
    if {[winfo exists [set w .@dls$net]]} {
	popup $w
	switch -- $args {} {
	    foreach x [winfo children $w] {
		catch {eval destroy [winfo children $x.bdy]}
		addSeparator $x.bdy -columnspan 3 -row 0
	    }
	}
    } {
	makeToplevel $w  "[$net name] - DCC Connections" {} {}
	wm resizable $w 0 0
	foreach x {coff achat fOffer fSend fGet} {
	    set w1 [switchFrame $w $x $tls($x) 0]
	    addSeparator $w1 -columnspan 3
	    grid columnconfigure $w1 1 -weight 1
	}
    }
    upvar #0 Offer$net Offer Send$net Send Get$net Get AChat$net AChat
    set w1 $w.coff.bdy
    set grd 1
    grid columnconfigure $w1 0 -weight 1
    foreach nn [array names AChat] {
	set ix [newName dcc]
	grid [label $w1.l$ix -text [$nn name]] -row $grd -column 0\
	  -sticky w -padx 5 -columnspan 2
	grid [button $w1.b$ix -text [trans cancel] \
	  -command "dccClose $net Call $nn {} $ix $w1"] -row $grd \
	  -column 2 -sticky ew
	incr grd
    }
    doTitle $grd $w coff $net
    set w1 $w.achat.bdy
    set grd 1
    foreach nn [$net chats] {
	switch -- [set nm [$nn name]] *default* {} default {
	    set ix [newName dcc]
	    grid [label $w1.l$ix -text $nm] -row $grd \
	      -column 0 -sticky w -padx 5 -columnspan 2
	    grid [button $w1.b$ix -text [trans cancel] \
	      -command "dccClose $net Chat $nn {} $ix $w1"] \
	      -sticky ew -row $grd -column 2
	    incr grd
	}
    }
    doTitle $grd $w achat $net
    foreach arr {Offer Send Get} {
	set w1 $w.f$arr.bdy
	grid columnconfigure $w1 1 -weight 1
	set grd 1
	foreach nn [array names $arr] {
	   # FRINK : nocheck
	   foreach fl [set ${arr}($nn)] {
		set ix [newName dcc]
		grid [label $w1.l$ix -text [$nn name]] \
		  -row $grd -column 0 -sticky w -padx 5
		grid [label $w1.f$ix -text [lindex $fl 1]] \
		  -row $grd -column 1 -sticky w -padx 5
		grid [button $w1.b$ix -text [trans cancel] \
		  -command "dccClose $net $arr $nn [list [lindex $fl 1]] $ix $w1"]\
		  -sticky ew -row $grd -column 2
		incr grd
	    }
	}
	doTitle $grd $w f$arr $net
    }
}
#
proc usersDCC {net cmd} {
    switch $cmd {
    List - Close { buildDCCList $net }
    default {
	    mkEntryBox .@$cmd $cmd "Enter user name for DCC $cmd:" \
	      [list [list user [$net lastuser]]] \
	      [list ok "after 0 doDCC $net [string toupper $cmd]"]\
	      [list cancel {}]
	}
    }
}
#
proc dccCheck {net interval} {
    upvar #0 Offer$net Offer
    set chng 0
    set tm [$net dccTime]
    foreach {x y} [array get Offer] {
	set dy {}
	foreach off $y {
	    set time [lindex $off 5]
	    if {[incr time $interval] < $tm} {	    
		lappend dy [lreplace $off 5 5 $time]
	    } {
		set chng 1
		catch {close [lindex $off 2]}
	    }
	}
	switch {} [set Offer($x) $dy] {unset Offer($x)}
    }
    global DCCList
    set dy {}
    set del {}
    set indx 0
    if {[info exists DCCList($net)]} {
	foreach x $DCCList($net) {
	    switch [lindex $x 1] {
	    Chat -
	    Send {
		set time [lindex $x 8]
		if {[incr time $interval] < 600000} {	    
		    lappend dy [lreplace $x 8 8 $time]
		} {
		    set chng 1
		    lappend del [list [lindex $x 0] [lindex $x 2]]
		}
		continue
	    }
	    default { lappend dy $x }
	    }
	    incr indx
	}
	switch {} [set DCCList($net) $dy] {catch {destroy .@drq$net}}
    }
    if {$chng} {
	if {[winfo exists .@dls$net]} {buildDCCList $net}
	if {[winfo exists .@drq$net]} {
	    foreach x $del {dccDel $net .@drq$net [lindex $x 0] [lindex $x 1]}
	}
    }
}
#
proc net_dccClean {this} {
    foreach x {Get Offer Send} {
	upvar #0 $x$this arr
	catch {
	    foreach {n m} [array get arr] {
		foreach v $m {killDel $x$this $n [lindex $v 1]}
	    }
	}
    }
    catch {destroy .@dls$this}
    set DCCList($this) {}
    catch {destroy .@drq$this}
}
#
proc user_dcc {this cmd} {
    set nk [$this name]
    set net [$this net]
    switch $cmd {
    SEND {
	    switch 7.5 [info tclversion] {
		mkFileBox {} SendDir($net) .* "Send $nk" \
		  "File to send to $nk" {} \
		  [list send "DCCSend $this"] [list cancel {}]
	    } default {
		global SendDir zFileTypes
		if {[catch {set dir $SendDir($net)}]} {
		    set SendDir($net) [set dir [pwd]]
		}
		if {[catch {switch {} [set fl [tk_getOpenFile -initialdir $dir \
		  -title "DCC send file to $nk" -filetypes $zFileTypes]] {
# user hit cancel
		} default {
		    set SendDir($net) [file dirname $fl]
		    DCCSend $this $fl
		}} err]} {
		    $net errmsg "File error: $err"
		}
	    }
	}
    CHAT {
	    upvar #0 AChat$net AChat
	    if {[info exist AChat($this)]} {
		if {[askUser {} Chat  \
		  "You already have a chat request pending for $nk. Close it?"]} {
		    $this unChat
		}
	    } \
	    elseif {[string compare nil [set ff [Chat :: find $nk [$this net]]]]} {
		if {[askUser {} Chat  \
		  "You already have a chat request pending for $nk. Close it?"]} {
		    $this unChat
		}
	    } \
	    elseif {[catch {ChatServer $this $nk} msg]}  {
		$net errmsg "[ipAddress] : $msg"
	    } \
	    elseif {[winfo exists .@dls$net ]} { buildDCCList $net }
	}
    }
}
#
proc unsetSGO {arr elem op} { $elem deref }
#
proc dccProgress {conn bytes pcnt min sec} {
    if {[winfo exists [set w .@$conn].txt]} {
	$w.txt.bytes configure -text "$bytes bytes transferred."
	$w.txt.pcnt configure -text "[format %3.2f $pcnt]% complete"
	$w.slide configure -state normal
	$w.slide set $pcnt
	$w.slide configure -state disabled
	update idletasks
    }
}
#
proc dccWindow {conn usr array file} {
    if {[winfo exists [set w .@$conn]]} return
    switch $array Send {
	set tl "DCC send $file to [$usr name]"
    } default {
	set tl "DCC get $file from [$usr name]"
    }
    makeToplevel $w $tl {} {}
    scale $w.slide -from 0 -to 100 -tickinterval 20 \
       -state disabled -length 200 -orient horizontal
    grid columnconfigure $w 0 -weight 1
    grid $w.slide -padx 10 -pady 5 -sticky ew
    frame $w.txt
    grid columnconfigure $w.txt 0 -weight 1
    grid columnconfigure $w.txt 1 -weight 1
    grid [label $w.txt.bytes -text {0 bytes transferred}] \
      [label $w.txt.pcnt -text {0% complete}] -padx 10 -sticky ew
    grid $w.txt -sticky ew -pady 5
    button $w.cancel -text [trans cancel] \
      -command "killDel $array[$usr net] $usr [list $file] ; destroy .@$conn"
    grid $w.cancel -sticky ew
}
