# $Id: s.Ftp.xotcl 1.8 01/03/23 21:55:31+01:00 neumann@somewhere.wu-wien.ac.at $
package provide xotcl::comm::ftp 0.9
package require xotcl::comm::httpAccess

Class Ftp -superclass NetAccess -parameter {user passwd}
Ftp instproc initialize args {
  #[self] showCall
  [self] instvar port caching user passwd loginMsg resp blocksize
  set port 21
  set blocksize 1024
  set caching 0
  set user ftp
  set passwd cineast@
  set loginMsg {}
  set resp(connect)       {220 provideUser}
  set resp(provideUser)   {331 providePasswd}
  set resp(providePasswd) {230 loginFinished}
  set resp(loginFinished) {227 pasv}
  set resp(pasv)          {200 type}
  set resp(type-list)     {150 list}
  set resp(type-retr)     {150 retr 550 retry-retrieve}
  set resp(transfer)      {226 transferDone}
  [self] next
}
Ftp instproc err {state reply} {
  [self] abort "Error in $state: $reply"
}
Ftp instproc queryServer {query state} {
  [self] instvar S
  puts $S $query
  flush $S
  fileevent $S readable [::list [self] response $state]
}
Ftp instproc response {state} {
  #[self] showCall
  [self] instvar S code msg
  set reply [gets $S]
  #[self] showVars reply
  if {[regexp {^([0-9]+)[-](.*)$} $reply _ code msg]} {
    fileevent $S readable [::list [self] responseMulti $state]
  } else {
    regexp {^([0-9]+) (.*)$} $reply _ code msg 
    [self] responseEnd $state
  }
}
Ftp instproc responseMulti {state} {
  # multi line response
  [self] instvar S code msg 
  set m [gets $S]
  if {[regexp "^$code " $m]} { 
    [self] responseEnd $state
  } else {
    # try to strip code and dash
    regexp "^$code-(.*)\$" $m _ m
    append msg \n$m
  }
}
Ftp instproc responseEnd {state} {
  [self] instvar S code msg resp
  fileevent $S readable {}
  #puts stderr "code=$code, msg=<$msg>"
  foreach {c newState} $resp($state) {
    if {$c == $code} { return [[self] $newState] }
  }
  [self] err $state "expected=$resp($state), got $code $msg"
}
Ftp instproc GET {} {
  [self] instvar S  host port url
  regexp {^(.*):([0-9]+)$} $host _ host port
  [self] running
  # rb running [self] $url ;# ???
  # proxy ?
  set S [socket -async $host $port]
  fconfigure $S -blocking false -translation {auto crlf}
  fileevent $S readable [::list [self] response connect]
}
Ftp instproc provideUser {} {
  [self] instvar user msg loginMsg
  set loginMsg $msg
  [self] queryServer "USER $user" provideUser
}
Ftp instproc providePasswd {} {
  [self] instvar passwd
#  if {[pwdManager requirePasswd "Ftp $user\@$host" $user password]} {
#    [self] queryServer "PASS $password" providePasswd
#  }
  [self] queryServer "PASS $passwd" providePasswd
}
Ftp instproc loginFinished {} {  
  [self] instvar msg loginMsg
  append  loginMsg \n$msg
  [self] queryServer "PASV" loginFinished
}
Ftp instproc pasv {} {
  [self] instvar S D msg
  set d {([0-9]+)}
  if {[regexp "\[(]$d,$d,$d,$d,$d,$d" $msg _ 1 2 3 4 p1 p2]} {
    if {[catch {set D [socket -async $1.$2.$3.$4 [expr {$p1*256 + $p2}]]} err
	]} {
      return [[self] err $proc $err] 
    }
    fconfigure $D -blocking no -translation binary
  } else {
    return [[self] err $proc $msg] 
  }
  [self] queryServer "TYPE I" pasv
}
Ftp instproc type {} {
  [self] instvar path
  if {$path=={}} {
    [self] queryServer "LIST" type-list
  } elseif {[regexp /$ $path]} { 
    [self] queryServer "LIST $path" type-list
  } else {
    [self] queryServer "RETR $path" type-retr
  }
}
Ftp instproc retry-retrieve {} {
  [self] instvar path url
  append url /
  [self] queryServer "LIST $path/" type-list
}
Ftp instproc list {} {
  [self] instvar S D contentType
  set contentType text/dirlist
  [self] headerDone
  fileevent $S readable [::list [self] response transfer]
  fileevent $D readable [::list [self] readData]
}
Ftp instproc read {} {
  # the method read is called by the more general method readData
  [self] instvar D block blocksize
  if {[::eof $D]} {
    set block ""
    close $D
    unset D
  } else {
    #puts stderr blocksize=$blocksize
    set block [::read $D $blocksize]
    #puts stderr read:[string length $block]bytes
  }
}
Ftp instproc transferDone {} {
  [self] instvar D S
  if {[info exists D]} {
    fileevent $S readable {}
    set block ""
    close $D
    unset D
  } 
  [self] finish
}
Ftp instproc retr {} {
  [self] instvar S D msg totalsize contentType path
  regexp {[(]([0-9]+)[ ]+[Bb]ytes} $msg _ totalsize
  set contentType [Mime guessContentType $path]
  [self] headerDone
  if {[info exists S]} {
    # file dialog was not canceled
    fileevent $S readable [::list [self] response transfer]
    fileevent $D readable [::list [self] readData]
    fconfigure $D -translation binary
  }
}
