mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 06:00:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1510 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			1510 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| # zftp is a loadable module implementing an FTP client as a builtin
 | |
| # command so that you can use the shell command language and line
 | |
| # editing to make life easier.  If your system has dynamically
 | |
| # load libraries and zsh was compiled to use them, it is probably
 | |
| # somewhere where it can be loaded at run time.  Otherwise, it depends
 | |
| # whether the shell was compiled with zftp already built into it.
 | |
| #
 | |
| # Here is a suite of functions, plus assorted other code, to make
 | |
| # zftp work smoothly.
 | |
| #
 | |
| # Completion is implemented in a fairly natural way, except that
 | |
| # very little support has been provided for non-UNIX remote hosts.
 | |
| # On such machines, the safest thing to do is only try to complete
 | |
| # files in the current directory; this should be OK.
 | |
| #
 | |
| # Remote globbing for commands which retrieve files is also
 | |
| # implemented.  This can be done in two different ways.  The default
 | |
| # is for zsh to do the globbing locally.  The advantage is that full
 | |
| # zsh pattern matching (respecting the setting of extendedglob) is
 | |
| # possible, and no assumption (apart from the restrictions on
 | |
| # directory handling noted above) is made about the behaviour of the
 | |
| # server.  The disadvantage is that the entire filename list for the
 | |
| # current directory must be retrieved, and then zsh must laboriously
 | |
| # do pattern matching against every file, so it is potentially slow
 | |
| # for large directories.  Only the non-directory part of file names is
 | |
| # globbed.
 | |
| #
 | |
| # The alternative will be used if $zfrglob has non-zero length.
 | |
| # Zsh then sends the pattern to the server for globbing.  Best of
 | |
| # luck.
 | |
| #
 | |
| # To support remote globbing, some functions have been aliased
 | |
| # with 'noglob' in front.  Currently, this has a dire effect on
 | |
| # completion unless the completeinaliases option is set, so
 | |
| # it is set below.  This can conceivably cause you problems
 | |
| # if you expect completion for aliases automatically to give you
 | |
| # completion for the base command.  I suspect that most people
 | |
| # don't even know that happens.
 | |
| #
 | |
| # The following functions are provided.
 | |
| #
 | |
| # General status changing and displaying functions:
 | |
| # zfparams
 | |
| #   Simple front end to `zftp params', except it will automatically
 | |
| #   query host, user and password.  These are then stored to be
 | |
| #   used with a `zfopen' with no arguments.
 | |
| # zfopen [ host [ user ... ] ]
 | |
| #   Open a connection and login.  Unless the option -1 (once)
 | |
| #   is given, will store the parameters for the open (including
 | |
| #   a password which is prompted for and not echoed) so that
 | |
| #   if you call zfopen subsequently without arguments it will
 | |
| #   reopen the same connection.
 | |
| # zfanon anonftphost
 | |
| #   Open a connection for anonymous FTP.  Tries to guess an
 | |
| #   email address to use as the password, unless $EMAIL_ADDR is
 | |
| #   already set.  The first time, will tell you what it has guessed.
 | |
| #   It's rude to set EMAIL_ADDR=mozilla.
 | |
| # zfcd [ dir | old new ]
 | |
| #   Change directory on the server.  This tries to mimic the behaviour
 | |
| #   of the shell's cd.  In particular,
 | |
| #    zfcd           change to '~' on server, if it interprets it
 | |
| #    zfcd -         change to previous directory of current connection
 | |
| #    zfcd OLD NEW   change directory from fooOLDbar to fooNEWbar
 | |
| #   One piece of magic is builtin:  an initial part of the directory
 | |
| #   matching $HOME is translated back to `~'.  Most UNIX servers
 | |
| #   recognise the usual shell convention.  So things like `zfcd $PWD'
 | |
| #   is useful provide you are under your home directory and the
 | |
| #   structure on the remote machine mirrors that on the local.
 | |
| # zfhere
 | |
| #   Synonym for `zfcd $PWD', see above.
 | |
| # zfdir [args]
 | |
| #   Show a long diretory list of the remote connection.  Any
 | |
| #   arguments are passed on to the server, apart from options.
 | |
| #   Currently this always uses a pager to show the directory
 | |
| #   list.  Caching is implemented:  zfdir on its own always shows
 | |
| #   the current diretory, which is cached; zfdir with some other
 | |
| #   directory arguments shows that, which is cached separately
 | |
| #   and can be reviewed with `zfdir -r'.  Other options:
 | |
| #    -f  force reget, overriding the cache, in case something's changed
 | |
| #    -d  delete the cache, but don't show anything.
 | |
| #   To pass options to the server, use e.g. `zfdir -- -C'.
 | |
| #   This also has the zfcd ~ hack.
 | |
| # zfls [args]
 | |
| #   Short list of the long directory, depending on what [args]
 | |
| #   do to the server.  No options, no caching, no pager.
 | |
| # zftype [ a[scii] | i[mage] | b[inary] ]
 | |
| #   Set or display the transfer type; currently only ASCII
 | |
| #   and image (same as binary) types are supported.
 | |
| # zfclose
 | |
| #   Close the connection.
 | |
| # zfstat
 | |
| #   Print the zftp status from local variables; doesn't do any network
 | |
| #   operations unless -v is supplied, in which case the server is
 | |
| #   asked for its views on the status, too.
 | |
| #
 | |
| # Functions for retrieving data:
 | |
| #   All accept the following options:
 | |
| #    -G   Don't do remote globbing (see above); the default is to do it.
 | |
| #    -t   Try to set local files to the same time as the remote ones.
 | |
| #         Unfortunately we only know the remote time in GMT, so it's
 | |
| #         a little tricky and you need perl 5 (installed as `perl')
 | |
| #         for this to work.  Suggestions welcome.
 | |
| # zfget file1 file2 ...
 | |
| #   Retrieve each file from the server.  The remote file is the
 | |
| #   full name given, the local file is the non-directory part of that
 | |
| #   (assuming UNIX file paths).
 | |
| # zfuget file1 file2 ..
 | |
| #   Get with update.  Check remote and local sizes and times and
 | |
| #   retrieve files which are newer on the server.  Will query
 | |
| #   hard cases, which are where the remote file is newer but a
 | |
| #   different size, or is older but the same size.  With option -s
 | |
| #   (silent) assumes it's best to retrieve the files in both those
 | |
| #   cases.  With -v (may be combined with -s), print the information
 | |
| #   about the files being considered.
 | |
| # zfcget file1 ...
 | |
| #   Assuming file1 was incompletely retrieved, try to get the rest of
 | |
| #   it.  This relies on a normal UNIX server behaviour which is not
 | |
| #   as specified in the FTP standard and hence is not universal.
 | |
| # zfgcp file1 file2
 | |
| # zfgcp file1 file2 ... dir
 | |
| #   Get with the behaviour of cp, i.e. copy remote file1 to local
 | |
| #   file2, or get remote fileN into local diretory dir.
 | |
| #
 | |
| # Function for sending data:
 | |
| # zfput file1 file2 ...
 | |
| #   Put the local files onto the server under the same name.  The
 | |
| #   local files are exactly as given; the remote files are the
 | |
| #   non-diretory parts of that. 
 | |
| # zfuput file1 file2 ..
 | |
| #   Put the local files onto the server, with update.  Works
 | |
| #   similarly to zfuget.
 | |
| #
 | |
| # Utility functions:
 | |
| # zftp_chpwd
 | |
| #   Show the new directory when it changes; try to put it into
 | |
| #   an xterm on shelltool header.  Works best alongside chpwd.
 | |
| # zftp_progress
 | |
| #   Show the percentage of a file retrieved as it is coming; if the
 | |
| #   size is not available show the size transferred so far.  The
 | |
| #   percentage may be wrong if sending data from a local pipe.
 | |
| #   If you transfer files in the background, you should undefine
 | |
| #   this before the transfer.  It is smart enough not to print
 | |
| #   anything when stderr is not a terminal.
 | |
| # zfcd_match
 | |
| #   Function for remote directory completion.
 | |
| # zfget_match
 | |
| #   Function for remote filename completion.
 | |
| # zfrglob varname
 | |
| #   This is used for the remote globbing.  The pattern resides
 | |
| #   in $varname (note extra level of indirection), and on return
 | |
| #   $varname will contain the list of matching files.
 | |
| # zfrtime locfile remfile [ time ]
 | |
| #   This sad thing does the setting of local file times to those
 | |
| #   of the remote, see horror story above.
 | |
| 
 | |
| zmodload -ia zftp
 | |
| 
 | |
| alias zfcd='noglob zfcd'
 | |
| alias zfget='noglob zfget'
 | |
| alias zfls='noglob zfls'
 | |
| alias zfdir='noglob zfdir'
 | |
| alias zfuget='noglob zfuget'
 | |
| # only way of getting that noglob out of the way at the moment
 | |
| setopt completealiases
 | |
| 
 | |
| #
 | |
| # zftp completions: only use these if new-style completion is not
 | |
| # active.
 | |
| #
 | |
| if [[ ${#patcomps} -eq 0 || ${patcomps[(i)zf*]} -gt ${#patcomps} ]]; then
 | |
|   compctl -f -x 'p[1]' \
 | |
|     -k '(open params user login type ascii binary mode put putat
 | |
|     get getat append appendat ls dir local remote mkdir rmdir delete
 | |
|     close quit)'  - \
 | |
|     'w[1,cd][1,ls][1,dir][1,rmdir]' -K zfcd_match -S/ -q - \
 | |
|     'W[1,get*]' -K zfget_match - 'w[1,delete][1,remote]' -K zfget_match - \
 | |
|     'w[1,open][1,params]' -k hosts -- zftp
 | |
|   compctl -K zfcd_match -S/ -q zfcd zfdir zfls
 | |
|   compctl -K zfget_match zfget zfgcp zfuget zfcget
 | |
|   compctl -k hosts zfanon zfopen zfparams
 | |
| fi
 | |
| 
 | |
| function zfanon {
 | |
|   local opt optlist once
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $optlist[$i] in
 | |
|         1) once=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   if [[ -z $EMAIL_ADDR ]]; then
 | |
|     # Exercise in futility.  There's a poem by Wallace Stevens
 | |
|     # called something like `N ways of looking at a blackbird',
 | |
|     # where N is somewhere around 0x14 to 0x18.  Now zftp is
 | |
|     # ashamed to prsent `N ways of looking at a hostname'.
 | |
|     local domain host
 | |
|     # First, maybe we've already got it.  Zen-like.
 | |
|     if [[ $HOST = *.* ]]; then
 | |
|       # assume this is the full host name
 | |
|       host=$HOST
 | |
|     elif [[ -f /etc/resolv.conf ]]; then
 | |
|       # Next, maybe we've got resolv.conf.
 | |
|       domain=$(awk '/domain/ { print $2 }' /etc/resolv.conf)
 | |
|       [[ -n $domain ]] && host=$HOST.$domain
 | |
|     fi
 | |
|     # Next, maybe we've got nlsookup.  May not work on LINUX.
 | |
|     [[ -z $host ]] && host=$(nslookup $HOST | awk '/Name:/ { print $2 }')
 | |
|     if [[ -z $host ]]; then
 | |
|       # we're running out of ideas, but this should work.
 | |
|       # after all, i wrote it...
 | |
|       # don't want user to know about this, too embarrassed.
 | |
|       local oldvb=$ZFTP_VERBOSE oldtm=$ZFTP_TMOUT
 | |
|       ZFTP_VERBOSE=
 | |
|       ZFTP_TMOUT=5
 | |
|       if zftp open $host >& /dev/null; then
 | |
|         host=$ZFTP_HOST
 | |
|         zftp close $host
 | |
|       fi
 | |
|       ZFTP_VERBOSE=$oldvb
 | |
|       ZFTP_TMOUT=$oldtm
 | |
|     fi
 | |
|     if [[ -z $host ]]; then
 | |
|       print "Can't get your hostname.  Define \$EMAIL_ADDR by hand."
 | |
|       return 1;
 | |
|     fi
 | |
|     EMAIL_ADDR="$USER@$host"
 | |
|     print "Using $EMAIL_ADDR as anonymous FTP password."
 | |
|   fi
 | |
|   
 | |
|   if [[ $once = 1 ]]; then
 | |
|     zftp open $1 anonymous $EMAIL_ADDR
 | |
|   else
 | |
|     zftp params $1 anonymous $EMAIL_ADDR
 | |
|     zftp open
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zfautocheck {
 | |
|   # This function is used to implement auto-open behaviour.
 | |
|   #
 | |
|   # With first argument including n, don't change to the old directory; else do.
 | |
|   #
 | |
|   # Set do_close to 1 if the connection was not previously open, 0 otherwise
 | |
|   # With first arguemnt including d, don't set do_close to 1.  Broadly
 | |
|   # speaking, we use this mechanism to shut the connection after use
 | |
|   # if the connection had been explicitly closed (i.e. didn't time out,
 | |
|   # which zftp test investigates) and we are not using a directory
 | |
|   # command, which implies we are looking for something so should stay open
 | |
|   # for it.
 | |
|   
 | |
|   # Remember the old session:  zflastsession will be overwritten by
 | |
|   # a successful open.
 | |
|   local lastsession=$zflastsession
 | |
|   
 | |
|   if [[ -z $ZFTP_HOST ]]; then
 | |
|     zfopen || return 1
 | |
|     [[ $1 = *d* ]] || do_close=1
 | |
|   elif zftp test 2>/dev/null; then
 | |
|     return 0
 | |
|   else
 | |
|     zfopen || return 1
 | |
|   fi
 | |
|   
 | |
|   if [[ $1 = *n* ]]; then
 | |
|     return 0
 | |
|   else
 | |
|     zfcd ${lastsession#*:}
 | |
|   fi
 | |
|   
 | |
| }
 | |
| 
 | |
| function zfcd {
 | |
|   # zfcd:  change directory on the remote server.
 | |
|   #
 | |
|   #  Currently has the following features:
 | |
|   # --- an initial string matching $HOME in the directory is turned back into ~
 | |
|   #     to be re-interpreted by the remote server.
 | |
|   # --- zfcd with no arguments changes directory to '~'
 | |
|   # --- `zfcd old new' and `zfcd -' work analagously to cd
 | |
|   # --- if the connection is not currently open, it will try to
 | |
|   #     re-open it with the stored parameters as set by zfopen.
 | |
|   #     If the connection timed out, however, it won't know until
 | |
|   #     too late.  In that case, just try the same zfcd command again
 | |
|   #     (but now `zfcd -' and `zfcd old new' won't work).
 | |
|   
 | |
|   # hack: if directory begins with $HOME, turn it back into ~
 | |
|   # there are two reasons for this:
 | |
|   #   first, a ~ on the command line gets expanded even with noglob.
 | |
|   #     (I suppose this is correct, but I wouldn't like to swear to it.)
 | |
|   #   second, we can no do 'zfcd $PWD' and the like, and that will
 | |
|   #     work just as long as the directory structures under the home match.
 | |
|   
 | |
|   if [[ $1 = /* ]]; then
 | |
|     zfautocheck -dn
 | |
|   else
 | |
|     zfautocheck -d
 | |
|   fi
 | |
|   
 | |
|   if [[ $1 = $HOME || $1 = $HOME/* ]]; then
 | |
|     1="~${1#$HOME}"
 | |
|   fi
 | |
|   
 | |
|   if (( $# == 0 )); then
 | |
|     # Emulate `cd' behaviour
 | |
|     set -- '~'
 | |
|   elif [[ $# -eq 1 && $1 = - ]]; then
 | |
|     # Emulate `cd -' behaviour.
 | |
|     set -- $zflastdir
 | |
|   elif [[ $# -eq 2 ]]; then
 | |
|     # Emulate `cd old new' behaviour.
 | |
|     # We have to find a character not in $1 or $2; ! is a good bet.
 | |
|     eval set -- "\${ZFTP_PWD:s!$1!$2!}"
 | |
|   fi
 | |
|   
 | |
|   # We have to remember the current directory before changing it
 | |
|   # if we want to keep it.
 | |
|   local lastdir=$ZFTP_PWD
 | |
|   
 | |
|   zftp cd "$@"  &&  zflastdir=$lastdir
 | |
|   print $zflastsession
 | |
| }
 | |
| 
 | |
| function zfcd_match {
 | |
|   # see zfcd for details of this hack
 | |
|   if [[ $1 = $HOME || $1 = $HOME/* ]]; then
 | |
|     1="~${1#$HOME}"
 | |
|   fi
 | |
|   
 | |
|   # error messages only
 | |
|   local ZFTP_VERBOSE=45
 | |
|   # should we redirect 2>/dev/null or let the user see it?
 | |
|   
 | |
|   local tmpf=${TMPPREFIX}zfcm$$
 | |
|   
 | |
|   if [[ $ZFTP_SYSTEM = UNIX* ]]; then
 | |
|     # hoo, aren't we lucky: this makes things so much easier
 | |
|     setopt localoptions rcexpandparam
 | |
|     local dir
 | |
|     if [[ $1 = ?*/* ]]; then
 | |
|       dir=${1%/*}
 | |
|     elif [[ $1 = /* ]]; then
 | |
|       dir=/
 | |
|     fi
 | |
|     # If we're using -F, we get away with using a directory
 | |
|     # to list, but not a glob.  Don't ask me why.
 | |
|     # I hate having to rely on awk here.
 | |
|     zftp ls -F $dir >$tmpf
 | |
|     reply=($(awk '/\/$/ { print substr($1, 0, length($1)-1) }' $tmpf))
 | |
|     rm -f $tmpf
 | |
|     if [[ $dir = / ]]; then
 | |
|       reply=(${dir}$reply)
 | |
|     elif [[ -n $dir ]]; then
 | |
|       reply=($dir/$reply)
 | |
|     fi
 | |
|   else
 | |
|     # I simply don't know what to do here.
 | |
|     # Just use the list of files for the current directory.
 | |
|     zfget_match $*
 | |
|   fi
 | |
|   
 | |
| }
 | |
| 
 | |
| function zfcget {
 | |
|   # Continuation get of files from remote server.
 | |
|   # For each file, if it's shorter here, try to get the remainder from
 | |
|   # over there.  This requires the server to support the REST command
 | |
|   # in the way many do but RFC959 doesn't specify.
 | |
|   # Options:
 | |
|   #   -G   don't to remote globbing, else do
 | |
|   #   -t   update the local file times to the same time as the remote.
 | |
|   #        Currently this only works if you have the `perl' command,
 | |
|   #        and that perl is version 5 with the standard library.
 | |
|   #        See the function zfrtime for more gory details.
 | |
|   
 | |
|   setopt localoptions
 | |
|   unsetopt ksharrays shwordsplit
 | |
|   
 | |
|   local loc rem stat=0 optlist opt nglob remlist locst remst
 | |
|   local tmpfile=${TMPPREFIX}zfcget$$ rstat tsize time
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $optlist[$i] in
 | |
|         G) nglob=1
 | |
|   	 ;;
 | |
|         t) time=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   for remlist in $*; do
 | |
|     # zfcd directory hack to put the front back to ~
 | |
|     if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
 | |
|       remlist="~${remlist#$HOME}"
 | |
|     fi
 | |
|     if [[ $nglob != 1 ]]; then
 | |
|       zfrglob remlist
 | |
|     fi
 | |
|     if (( $#remlist )); then
 | |
|       for rem in $remlist; do
 | |
|         loc=${rem:t}
 | |
|         if [[ ! -f $loc ]]; then
 | |
|   	# File does not yet exist
 | |
|   	zftp get $rem >$loc || stat=$?
 | |
|         else
 | |
|   	# Compare the sizes.
 | |
|   	locst=($(zftp local $loc))
 | |
|   	zftp remote $rem >$tmpfile
 | |
|   	rstat=$?
 | |
|   	remst=($(<$tmpfile))
 | |
|   	rm -f $tmpfile
 | |
|   	if [[ $rstat = 2 ]]; then
 | |
|   	  print "Server does not support SIZE command.\n" \
 | |
|   	  "Assuming you know what you're doing..." 2>&1
 | |
|   	  zftp getat $rem $locst[1] >>$loc || stat=$?
 | |
|   	  continue
 | |
|   	elif [[ $rstat = 1 ]]; then
 | |
|   	  print "Remote file not found: $rem" 2>&1
 | |
|   	  continue
 | |
|   	fi
 | |
|   	if [[ $locst[1] -gt $remst[1] ]]; then
 | |
|   	  print "Local file is larger!" 2>&1
 | |
|   	  continue;
 | |
|   	elif [[ $locst[1] == $remst[1] ]]; then
 | |
|   	  print "Files are already the same size." 2>&1
 | |
|   	  continue
 | |
|   	else
 | |
|   	  if zftp getat $rem $locst[1] >>$loc; then
 | |
|   	    [[ $time = 1 ]] && zfrtime $loc $rem $remst[2]
 | |
|   	  else
 | |
|   	    stat=1
 | |
|   	  fi
 | |
|   	fi
 | |
|         fi
 | |
|       done
 | |
|     fi
 | |
|   done
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfclose {
 | |
|   zftp close
 | |
| }
 | |
| 
 | |
| function zfcput {
 | |
|   # Continuation put of files from remote server.
 | |
|   # For each file, if it's shorter over there, put the remainder from
 | |
|   # over here.  This uses append, which is standard, so unlike zfcget it's
 | |
|   # expected to work on any reasonable server... err, as long as it
 | |
|   # supports SIZE and MDTM.  (It could be enhanced so you can enter the
 | |
|   # size so far by hand.)  You should probably be in binary transfer
 | |
|   # mode, thought it's not enforced.
 | |
|   #
 | |
|   # To read from midway through a local file, `tail +<n>c' is used.
 | |
|   # It would be nice to find a way of doing this which works on all OS's.
 | |
|   
 | |
|   setopt localoptions
 | |
|   unsetopt ksharrays shwordsplit
 | |
|   
 | |
|   local loc rem stat=0 locst remst offs tailtype
 | |
|   local tmpfile=${TMPPREFIX}zfcget$$ rstat
 | |
|   
 | |
|   # find how tail works.  this is intensely annoying, since it's completely
 | |
|   # standard in C.  od's no use, since we can only skip whole blocks.
 | |
|   if [[ $(echo abcd | tail +2c) = bcd ]]; then
 | |
|     tailtype=c
 | |
|   elif [[ $(echo abcd | tail --bytes=+2) = bcd ]]; then
 | |
|     tailtype=b
 | |
|   else
 | |
|     print "I can't get your \`tail' to start from from arbitrary characters.\n" \
 | |
|     "If you know how to do this, let me know." 2>&1
 | |
|     return 1
 | |
|   fi
 | |
|   
 | |
|   for loc in $*; do
 | |
|     # zfcd directory hack to put the front back to ~
 | |
|     rem=$loc
 | |
|     if [[ $rem = $HOME || $rem = $HOME/* ]]; then
 | |
|       rem="~${rem#$HOME}"
 | |
|     fi
 | |
|     if [[ ! -r $loc ]]; then
 | |
|       print "Can't read file $loc"
 | |
|       stat=1
 | |
|     else
 | |
|       # Compare the sizes.
 | |
|       locst=($(zftp local $loc))
 | |
|       zftp remote $rem >$tmpfile
 | |
|       rstat=$?
 | |
|       remst=($(<$tmpfile))
 | |
|       rm -f $tmpfile
 | |
|       if [[ $rstat = 2 ]]; then
 | |
|         print "Server does not support remote status commands.\n" \
 | |
|         "You will have to find out the size by hand and use zftp append." 2>&1
 | |
|         stat=1
 | |
|         continue
 | |
|       elif [[ $rstat = 1 ]]; then
 | |
|         # Not found, so just do a standard put.
 | |
|         zftp put $rem <$loc
 | |
|       elif [[ $remst[1] -gt $locst[1] ]]; then
 | |
|         print "Remote file is larger!" 2>&1
 | |
|         continue;
 | |
|       elif [[ $locst[1] == $remst[1] ]]; then
 | |
|         print "Files are already the same size." 2>&1
 | |
|         continue
 | |
|       else
 | |
|         # tail +<N>c takes the count of the character
 | |
|         # to start from, not the offset from zero. if we did
 | |
|         # this with years, then 2000 would be 1999.  no y2k bug!
 | |
|         # brilliant.
 | |
|         (( offs = $remst[1] + 1 ))
 | |
|         if [[ $tailtype = c ]]; then
 | |
|   	tail +${offs}c $loc | zftp append $rem || stat=1
 | |
|         else
 | |
|   	tail --bytes=+$offs $loc | zftp append $rem || stat=1
 | |
|         fi
 | |
|       fi
 | |
|     fi
 | |
|   done
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfdir {
 | |
|   # Long directory of remote server.
 | |
|   # The remote directory is cached.  In fact, two caches are kept:
 | |
|   # one of the standard listing of the current directory, i.e. zfdir
 | |
|   # with no arguments, and another for everything else.
 | |
|   # To access the appropriate cache, just use zfdir with the same
 | |
|   # arguments as previously.  zfdir -r will also re-use the `everything
 | |
|   # else' cache; you can always reuse the current directory cache just
 | |
|   # with zfdir on its own.
 | |
|   #
 | |
|   # The current directory cache is emptied when the directory changes;
 | |
|   # the other is kept until a new zfdir with a non-empty argument list.
 | |
|   # Both are removed when the connection is closed.
 | |
|   #
 | |
|   # zfdir -f will force the existing cache to be ignored, e.g. if you know
 | |
|   #          or suspect the directory has changed.
 | |
|   # zfdir -d will remove both caches without listing anything.
 | |
|   # If you need to pass -r, -f or -d to the dir itself, use zfdir -- -d etc.;
 | |
|   # unrecognised options are passed through to dir, but zfdir options must
 | |
|   # appear first and unmixed with the others.
 | |
|   
 | |
|   setopt localoptions unset extendedglob
 | |
|   unsetopt shwordsplit ksharrays
 | |
|   
 | |
|   local file opt optlist redir i newargs force
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     elif [[ $1 != -[rfd]## ]]; then
 | |
|       # pass options through to ls
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $optlist[$i] in
 | |
|         r) redir=1
 | |
|   	 ;;
 | |
|         f) force=1
 | |
|   	 ;;
 | |
|         d) [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
 | |
|   	 [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
 | |
|   	 zftp_fcache=()
 | |
|   	 return 0
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   zfautocheck -d
 | |
|   
 | |
|   # directory hack, see zfcd
 | |
|   for (( i = 1; i <= $#argv; i++ )); do
 | |
|     if [[ $argv[$i] = $HOME || $argv[$i] = $HOME/* ]]; then
 | |
|       argv[$i]="~${argv[$i]#$HOME}"
 | |
|     fi
 | |
|   done
 | |
|   
 | |
|   if [[ $# -eq 0 ]]; then
 | |
|     # Cache it in the current directory file.  This means that repeated
 | |
|     # calls to zfdir with no arguments always use a cached file.
 | |
|     [[ -z $zfcurdir ]] && zfcurdir=${TMPPREFIX}zfcurdir$$
 | |
|     file=$zfcurdir
 | |
|   else
 | |
|     # Last directly looked at was not the current one, or at least
 | |
|     # had non-standard arguments.
 | |
|     [[ -z $zfotherdir ]] && zfotherdir=${TMPPREFIX}zfotherdir$$
 | |
|     file=$zfotherdir
 | |
|     newargs="$*"
 | |
|     if [[ -f $file && $redir != 1 && $force -ne 1 ]]; then
 | |
|       # Don't use the cached file if the arguments changed.
 | |
|       [[ $newargs = $zfotherargs ]] || rm -f $file
 | |
|     fi
 | |
|     zfotherargs=$newargs
 | |
|   fi
 | |
|   
 | |
|   if [[ $force -eq 1 ]]; then
 | |
|     rm -f $file
 | |
|     # if it looks like current directory has changed, better invalidate
 | |
|     # the filename cache, too.
 | |
|     (( $# == 0 )) && zftp_fcache=()
 | |
|   fi
 | |
|   
 | |
|   if [[ -n $file && -f $file ]]; then
 | |
|     eval ${PAGER:-more} \$file
 | |
|   else
 | |
|     if (zftp test); then
 | |
|       # Works OK in subshells
 | |
|       zftp dir $* | tee $file | eval ${PAGER-:more}
 | |
|     else
 | |
|       # Doesn't work in subshells (IRIX 6.2 --- why?)
 | |
|       zftp dir $* >$file
 | |
|       eval ${PAGER-:more} >$file
 | |
|     fi
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zfgcp {
 | |
|   # ZFTP get as copy:  i.e. first arguments are remote, last is local.
 | |
|   # Supposed to work exactly like a normal copy otherwise, i.e.
 | |
|   #  zfgcp rfile lfile
 | |
|   # or
 | |
|   #  zfgcp rfile1 rfile2 rfile3 ... ldir
 | |
|   # Options:
 | |
|   #   -G   don't to remote globbing, else do
 | |
|   #   -t   update the local file times to the same time as the remote.
 | |
|   #        Currently this only works if you have the `perl' command,
 | |
|   #        and that perl is version 5 with the standard library.
 | |
|   #        See the function zfrtime for more gory details.
 | |
|   #
 | |
|   # If there is no current connection, try to use the existing set of open
 | |
|   # parameters to establish one and close it immediately afterwards.
 | |
|   
 | |
|   setopt localoptions
 | |
|   unsetopt shwordsplit
 | |
|   
 | |
|   local opt optlist nglob remlist rem loc time
 | |
|   integer stat do_close
 | |
|   
 | |
|   while [[ $1 == -* ]]; do
 | |
|     if [[ $1 == - || $1 == -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $opt in
 | |
|         G) nglob=1
 | |
|   	 ;;
 | |
|         t) time=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   zfautocheck
 | |
|   
 | |
|   # hmm, we should really check this after expanding the glob,
 | |
|   # but we shouldn't expand the last argument remotely anyway.
 | |
|   if [[ $# -gt 2 && ! -d $argv[-1] ]]; then
 | |
|     print "zfgcp:  last argument must be a directory." 2>&1
 | |
|     return 1
 | |
|   elif [[ $# == 1 ]]; then
 | |
|     print "zfgcp:  not enough arguments." 2>&1
 | |
|     return 1
 | |
|   fi
 | |
|   
 | |
|   if [[ -d $argv[-1] ]]; then
 | |
|     local dir=$argv[-1]
 | |
|     argv[-1]=
 | |
|     for remlist in $*; do
 | |
|       # zfcd directory hack to put the front back to ~
 | |
|       if [[ $remlist = $HOME || $remlist = $HOME/* ]]; then
 | |
|         remlist="~${remlist#$HOME}"
 | |
|       fi
 | |
|       if [[ $nglob != 1 ]]; then
 | |
|         zfrglob remlist
 | |
|       fi
 | |
|       if (( $#remlist )); then
 | |
|         for rem in $remlist; do
 | |
|   	loc=$dir/${rem:t}
 | |
|   	if zftp get $rem >$loc; then
 | |
|   	  [[ $time = 1 ]] && zfrtime $rem $loc
 | |
|   	else
 | |
|   	  stat=1
 | |
|   	fi
 | |
|         done
 | |
|       fi
 | |
|     done
 | |
|   else
 | |
|     zftp get $1 >$2 || stat=$?
 | |
|   fi
 | |
|   
 | |
|   (( $do_close )) && zfclose
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfget {
 | |
|   # Get files from remote server.  Options:
 | |
|   #   -G   don't to remote globbing, else do
 | |
|   #   -t   update the local file times to the same time as the remote.
 | |
|   #        Currently this only works if you have the `perl' command,
 | |
|   #        and that perl is version 5 with the standard library.
 | |
|   #        See the function zfrtime for more gory details.
 | |
|   #
 | |
|   # If the connection is not currently open, try to open it with the current
 | |
|   # parameters (set by a previous zfopen or zfparams), then close it after
 | |
|   # use.  The file is put in the current directory (i.e. using the basename
 | |
|   # of the remote file only); for more control, use zfgcp.
 | |
|   
 | |
|   local loc rem optlist opt nglob remlist time
 | |
|   integer stat do_close
 | |
|   
 | |
|   while [[ $1 == -* ]]; do
 | |
|     if [[ $1 == - || $1 == -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $opt in
 | |
|         G) nglob=1
 | |
|   	 ;;
 | |
|         t) time=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   zfautocheck
 | |
|   
 | |
|   for remlist in $*; do
 | |
|     # zfcd directory hack to put the front back to ~
 | |
|     if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
 | |
|       remlist="~${remlist#$HOME}"
 | |
|     fi
 | |
|     if [[ $nglob != 1 ]]; then
 | |
|       zfrglob remlist
 | |
|     fi
 | |
|     if (( $#remlist )); then
 | |
|       for rem in $remlist; do
 | |
|         loc=${rem:t}
 | |
|         if zftp get $rem >$loc; then
 | |
|   	[[ $time = 1 ]] && zfrtime $rem $loc
 | |
|         else
 | |
|   	stat=1
 | |
|         fi
 | |
|       done
 | |
|     fi
 | |
|   done
 | |
|   
 | |
|   (( $do_close )) && zfclose
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfget_match {
 | |
|   # the zfcd hack:  this may not be necessary here
 | |
|   if [[ $1 == $HOME || $1 == $HOME/* ]]; then
 | |
|     1="~${1#$HOME}"
 | |
|   fi
 | |
|   
 | |
|   local tmpf=${TMPPREFIX}zfgm$$
 | |
|   
 | |
|   if [[ $ZFTP_SYSTEM == UNIX* && $1 == */* ]]; then
 | |
|     # On the first argument to ls, we usually get away with a glob.
 | |
|     zftp ls "$1*$2" >$tmpf
 | |
|     reply=($(<$tmpf))
 | |
|     rm -f $tmpf
 | |
|   else
 | |
|     if (( $#zftp_fcache == 0 )); then
 | |
|       # Always cache the current directory and use it
 | |
|       # even if the system is UNIX.
 | |
|       zftp ls >$tmpf
 | |
|       zftp_fcache=($(<$tmpf))
 | |
|       rm -f $tmpf
 | |
|     fi
 | |
|     reply=($zftp_fcache);
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zfhere {
 | |
|   # Change to the directory corresponding to $PWD on the server.
 | |
|   # See zfcd for how this works.
 | |
|   zfcd $PWD
 | |
| }
 | |
| 
 | |
| function zfls {
 | |
|   # directory hack, see zfcd
 | |
|   if [[ $1 = $HOME || $1 = $HOME/* ]]; then
 | |
|     1="~${1#$HOME}"
 | |
|   fi
 | |
|   
 | |
|   zfautocheck -d
 | |
|   
 | |
|   zftp ls $*
 | |
| }
 | |
| 
 | |
| function zfopen {
 | |
|   # Use zftp params to set parameters for open, rather than sending
 | |
|   # them straight to open.  That way they are stored for a future open
 | |
|   # command.
 | |
|   #
 | |
|   # With option -1 (just this 1ce), don't do that.
 | |
|   
 | |
|   local optlist opt once
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $optlist[$i] in
 | |
|         1) once=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   # This is where we should try and do same name-lookupage in
 | |
|   # both .netrc and .ncftp/bookmarks .  We could even try saving
 | |
|   # the info in their for new hosts, like ncftp does.
 | |
|   
 | |
|   if [[ $once = 1 ]]; then
 | |
|     zftp open $*
 | |
|   else
 | |
|     # set parameters, but only if there was at least a host
 | |
|     (( $# > 0 )) && zfparams $*
 | |
|     # now call with no parameters
 | |
|     zftp open
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zfparams {
 | |
|     # Set to prompt for any user or password if not given.
 | |
|     # Don't worry about accounts here.
 | |
|     if (( $# > 0 )); then
 | |
|       (( $# < 2 )) && 2='?'
 | |
|       (( $# < 3 )) && 3='?'
 | |
|     fi
 | |
|     zftp params $*
 | |
| }
 | |
| 
 | |
| function zfpcp {
 | |
|   # ZFTP put as copy:  i.e. first arguments are remote, last is local.
 | |
|   # Currently only supports
 | |
|   #  zfcp lfile rfile
 | |
|   # if and only if there are two arguments
 | |
|   # or
 | |
|   #  zfcp lfile1 lfile2 lfile3 ... rdir
 | |
|   # if and only if there are more than two (because otherwise it doesn't
 | |
|   # know if the last argument is a directory on the remote machine).
 | |
|   # argument.
 | |
|   
 | |
|   setopt localoptions
 | |
|   unsetopt shwordsplit
 | |
|   
 | |
|   local rem loc
 | |
|   integer stat do_close
 | |
|   
 | |
|   zfautocheck
 | |
|   
 | |
|   if (( $# > 2 )); then
 | |
|     local dir=$argv[-1]
 | |
|     argv[-1]=
 | |
|     # zfcd directory hack to put the front back to ~
 | |
|     if [[ $dir = $HOME || $dir = $HOME/* ]]; then
 | |
|       dir="~${dir#$HOME}"
 | |
|     fi
 | |
|     for loc in $*; do
 | |
|       rem=$dir/${loc:t}
 | |
|       zftp put $rem <$loc || stat=1
 | |
|     done
 | |
|   else
 | |
|     zftp put $2 <$1 || stat=$?
 | |
|   fi
 | |
|   
 | |
|   (( $do_close )) && zfclose
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfput {
 | |
|   # Simple put:  dump every file under the same name, but stripping
 | |
|   # off any directory parts.
 | |
|   
 | |
|   local loc rem
 | |
|   integer stat do_close
 | |
|   
 | |
|   zfautocheck
 | |
|   
 | |
|   for loc in $*; do
 | |
|     rem=${loc:t}
 | |
|     zftp put $rem <$loc
 | |
|     [[ $? == 0 ]] || stat=$?
 | |
|   done
 | |
|   
 | |
|   (( $do_close )) && zfclose
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfrglob {
 | |
|   # Do the remote globbing for zfput, etc.
 | |
|   # We have two choices:
 | |
|   #  (1) Get the entire file list and match it one by one
 | |
|   #      locally against the pattern.
 | |
|   #      Causes problems if we are globbing directories (rare, presumably).
 | |
|   #      But: we can cache the current directory, which
 | |
|   #      we need for completion anyway.  Works on any OS if you
 | |
|   #      stick with a single directory.  This is the default.
 | |
|   #  (2) Use remote globbing, i.e. pass it to ls at the site.
 | |
|   #      Faster, but only works with UNIX, and only basic globbing.
 | |
|   #      We do this if $zfrglob is non-null.
 | |
|   
 | |
|   # There is only one argument, the variable containing the
 | |
|   # pattern to be globbed.  We set this back to an array containing
 | |
|   # all the matches.
 | |
|   setopt localoptions unset
 | |
|   unsetopt ksharrays
 | |
|   
 | |
|   local pat dir nondir files i
 | |
|   
 | |
|   eval pat=\$$1
 | |
|   
 | |
|   # Check if we really need to do anything.  Look for standard
 | |
|   # globbing characters, and if extendedglob is set and we are
 | |
|   # using zsh for the actual pattern matching also look for
 | |
|   # extendedglob characters.
 | |
|   if [[ $pat != *[][*?]* &&
 | |
|     ( -n $zfrglob || ! -o extendedglob || $pat != *[(|)#^]* ) ]]; then
 | |
|     return 0
 | |
|   fi
 | |
|   local tmpf={$TMPPREFIX}zfrglob$$
 | |
|   
 | |
|   if [[ $zfrglob != '' ]]; then
 | |
|     zftp ls "$pat" >$tmpf 2>/dev/null
 | |
|     eval "$1=(\$(<\$tmpf))"
 | |
|     rm -f $tmpf
 | |
|   else
 | |
|     if [[ $ZFTP_SYSTEM = UNIX* && $pat = */* ]]; then
 | |
|       # not the current directory and we know how to handle paths
 | |
|       if [[ $pat = ?*/* ]]; then
 | |
|         # careful not to remove too many slashes
 | |
|         dir=${pat%/*}
 | |
|       else
 | |
|         dir=/
 | |
|       fi
 | |
|       nondir=${pat##*/}
 | |
|       zftp ls "$dir" 2>/dev/null >$tmpf
 | |
|       files=($(<$tmpf))
 | |
|       files=(${files:t})
 | |
|       rm -f $tmpf
 | |
|     else
 | |
|       # we just have to do an ls and hope that's right
 | |
|       nondir=$pat
 | |
|       if (( $#zftp_fcache == 0 )); then
 | |
|         # Why does `zftp_fcache=($(zftp ls))' sometimes not work?
 | |
|         zftp ls >$tmpf
 | |
|         zftp_fcache=($(<$tmpf))
 | |
|         rm -f $tmpf
 | |
|       fi
 | |
|       files=($zftp_fcache)
 | |
|     fi
 | |
|     # now we want to see which of the $files match $nondir
 | |
|     for (( i = 1; i <= $#files; i++)); do
 | |
|       # empty words are elided in array assignment
 | |
|       [[ $files[$i] = ${~nondir} ]] || files[$i]=''
 | |
|     done
 | |
|     eval "$1=(\$files)"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zfrtime {
 | |
|   # Set the modification time of file LOCAL to that of REMOTE.
 | |
|   # If the optional TIME is passed, it should be in the FTP format
 | |
|   # CCYYMMDDhhmmSS, i.e. no dot before the seconds, and in GMT.
 | |
|   # This is what both `zftp remote' and `zftp local' return.
 | |
|   #
 | |
|   # Unfortunately, since the time returned from FTP is GMT and
 | |
|   # your file needs to be set in local time, we need to do some
 | |
|   # hacking around with time.  At the moment this requires perl 5
 | |
|   # with the standard library.
 | |
|   
 | |
|   setopt localoptions unset
 | |
|   unsetopt ksharrays
 | |
|   
 | |
|   local time gmtime loctime
 | |
|   
 | |
|   if [[ -n $3 ]]; then
 | |
|     time=$3
 | |
|   else
 | |
|     time=($(zftp remote $2 2>/dev/null))
 | |
|     [[ -n $time ]] && time=$time[2]
 | |
|   fi
 | |
|   [[ -z $time ]] && return 1
 | |
|   
 | |
|   # Now's the real *!@**!?!.  We have the date in GMT and want to turn
 | |
|   # it into local time for touch to handle.  It's just too nasty
 | |
|   # to handle in zsh; do it in perl.
 | |
|   if perl -mTime::Local -e '($file, $t) = @ARGV;
 | |
|   $yr = substr($t, 0, 4) - 1900;
 | |
|   $mon = substr($t, 4, 2) - 1;
 | |
|   $mday = substr($t, 6, 2) + 0;
 | |
|   $hr = substr($t, 8, 2) + 0;
 | |
|   $min = substr($t, 10, 2) + 0;
 | |
|   $sec = substr($t, 12, 2) + 0;
 | |
|   $time = Time::Local::timegm($sec, $min, $hr, $mday, $mon, $yr);
 | |
|   utime $time, $time, $file and return 0;' $1 $time 2>/dev/null; then
 | |
|     print "Setting time for $1 failed.  Need perl 5." 2>1
 | |
|   fi
 | |
|   
 | |
|   # If it wasn't for the GMT/local time thing, it would be this simple.
 | |
|   #
 | |
|   # time="${time[1,12]}.${time[13,14]}"
 | |
|   #
 | |
|   # touch -t $time $1
 | |
|   
 | |
| }
 | |
| 
 | |
| function zfstat {
 | |
|   # Give a zftp status report using local variables.
 | |
|   # With option -v, connect to the remote host and ask it what it
 | |
|   # thinks the status is.  
 | |
|   
 | |
|   setopt localoptions unset
 | |
|   unsetopt ksharrays
 | |
|   
 | |
|   local i stat=0 opt optlist verbose
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $opt in
 | |
|         v) verbose=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   if [[ -n $ZFTP_HOST ]]; then
 | |
|     print "Host:\t\t$ZFTP_HOST"
 | |
|     print "IP:\t\t$ZFTP_IP"
 | |
|     [[ -n $ZFTP_SYSTEM ]] && print "System type:\t$ZFTP_SYSTEM"
 | |
|     if [[ -n $ZFTP_USER ]]; then
 | |
|       print "User:\t\t$ZFTP_USER "
 | |
|       [[ -n $ZFTP_ACCOUNT ]] && print "Account:\t$AFTP_ACCOUNT"
 | |
|       print "Directory:\t$ZFTP_PWD"
 | |
|       print -n "Transfer type:\t"
 | |
|       if [[ $ZFTP_TYPE = "I" ]]; then
 | |
|         print Image
 | |
|       elif [[ $ZFTP_TYPE = "A" ]]; then
 | |
|         print Ascii
 | |
|       else
 | |
|         print Unknown
 | |
|       fi
 | |
|       print -n "Transfer mode:\t"
 | |
|       if [[ $ZFTP_MODE = "S" ]]; then
 | |
|         print Stream
 | |
|       elif [[ $ZFTP_MODE = "B" ]]; then
 | |
|         print Block
 | |
|       else
 | |
|         print Unknown
 | |
|       fi
 | |
|     else
 | |
|       print "No user logged in."
 | |
|     fi
 | |
|   else
 | |
|     print "Not connected."
 | |
|     [[ -n $zflastsession ]] && print "Last session:\t$zflastsession"
 | |
|     stat=1
 | |
|   fi
 | |
|   
 | |
|   # things which may be set even if not connected:
 | |
|   [[ -n $ZFTP_REPLY ]] && print "Last reply:\t$ZFTP_REPLY"
 | |
|   print "Verbosity:\t$ZFTP_VERBOSE"
 | |
|   print "Timeout:\t$ZFTP_TMOUT"
 | |
|   print -n "Preferences:\t"
 | |
|   for (( i = 1; i <= ${#ZFTP_PREFS}; i++ )); do
 | |
|     case $ZFTP_PREFS[$i] in
 | |
|       [pP]) print -n "Passive "
 | |
|   	  ;;
 | |
|       [sS]) print -n "Sendport "
 | |
|   	  ;;
 | |
|       [dD]) print -n "Dumb "
 | |
|   	  ;;
 | |
|       *) print -n "$ZFTP_PREFS[$i]???"
 | |
|     esac
 | |
|   done
 | |
|   print
 | |
|   
 | |
|   if [[ -n $ZFTP_HOST && $verbose = 1 ]]; then
 | |
|     zfautocheck -d
 | |
|     print "Status of remote server:"
 | |
|     # make sure we print the reply
 | |
|     local ZFTP_VERBOSE=045
 | |
|     zftp quote STAT
 | |
|   fi
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zftp_chpwd {
 | |
|   # You may want to alter chpwd to call this when $ZFTP_USER is set.
 | |
|   
 | |
|   # Cancel the filename cache for the current directory.
 | |
|   zftp_fcache=()
 | |
|   # ...and also empty the stored directory listing cache.
 | |
|   # As this function is called when we close the connection, this
 | |
|   # is the only place we need to do these two things.
 | |
|   [[ -n $zfcurdir && -f $zfcurdir ]] && rm -f $zfcurdir
 | |
|   zfotherargs=
 | |
|   
 | |
|   if [[ -z $ZFTP_USER ]]; then
 | |
|     # last call, after an FTP logout
 | |
|   
 | |
|     # delete the non-current cached directory
 | |
|     [[ -n $zfotherdir && -f $zfotherdir ]] && rm -f $zfotherdir
 | |
|   
 | |
|     # don't keep zflastdir between opens (do keep zflastsession)
 | |
|     zflastdir=
 | |
|   
 | |
|     # return the display to standard
 | |
|     # uncomment the following line if you have a chpwd which shows directories
 | |
|     chpwd
 | |
|   else
 | |
|     [[ -n $ZFTP_PWD ]] && zflastdir=$ZFTP_PWD
 | |
|     zflastsession="$ZFTP_HOST:$ZFTP_PWD"
 | |
|     local args
 | |
|     if [[ -t 1 && -t 2 ]]; then
 | |
|       local str=$zflastsession
 | |
|       [[ ${#str} -lt 70 ]] && str="%m: %~  $str"
 | |
|       case $TERM in
 | |
|         sun-cmd) print -n -P "\033]l$str\033\\"
 | |
|   	       ;;
 | |
|         xterm) print -n -P "\033]2;$str\a"
 | |
|   	     ;;
 | |
|       esac
 | |
|     fi
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zftp_progress {
 | |
|   # Basic progress metre, showing the percent of the file transferred.
 | |
|   # You want growing bars?  You gotta write growing bars.
 | |
|   
 | |
|   # Don't show progress unless stderr is a terminal
 | |
|   [[ ! -t 2 ]] && return 0
 | |
|   
 | |
|   if [[ $ZFTP_TRANSFER = *F ]]; then
 | |
|     print 1>&2
 | |
|   elif [[ -n $ZFTP_TRANSFER ]]; then
 | |
|     if [[ -n $ZFTP_SIZE ]]; then
 | |
|       local frac="$(( ZFTP_COUNT * 100 / ZFTP_SIZE ))%"
 | |
|       print -n "\r$ZFTP_FILE ($ZFTP_SIZE bytes): $ZFTP_TRANSFER $frac" 1>&2
 | |
|     else
 | |
|       print -n "\r$ZFTP_FILE: $ZFTP_TRANSFER $ZFTP_COUNT" 1>&2
 | |
|     fi
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zftype {
 | |
|   local type zftmp=${TMPPREFIX}zftype$$
 | |
|   
 | |
|   zfautocheck -d
 | |
|   
 | |
|   if (( $# == 0 )); then
 | |
|     zftp type >$zftmp
 | |
|     type=$(<$zftmp)
 | |
|     rm -f $zftmp
 | |
|     if [[ $type = I ]]; then
 | |
|       print "Current type is image (binary)"
 | |
|       return 0
 | |
|     elif [[ $type = A ]]; then
 | |
|       print "Current type is ASCII"
 | |
|       return 0
 | |
|     else
 | |
|       return 1
 | |
|     fi
 | |
|   else
 | |
|     if [[ $1 == [aA]([sS][cC]([iI][iI]|)|) ]]; then
 | |
|       type=A
 | |
|     elif [[ $1 == [iI]([mM]([aA][gG][eE]|)|) ||
 | |
|       $1 == [bB]([iI][nN]([aA][rR][yY]|)|) ]]; then
 | |
|       type=I
 | |
|     else
 | |
|       print "Type not recognised:  $1" 2>&1
 | |
|       return 1
 | |
|     fi
 | |
|     zftp type $type
 | |
|   fi
 | |
| }
 | |
| 
 | |
| function zfuget {
 | |
|   # Get a list of files from the server with update.
 | |
|   # In other words, only retrieve files which are newer than local
 | |
|   # ones.  This depends on the clocks being adjusted correctly
 | |
|   # (i.e. if one is fifteen minutes out, for the next fifteen minutes
 | |
|   # updates may not be correctly calculated).  However, difficult
 | |
|   # cases --- where the files are the same size, but the remote is newer,
 | |
|   # or have different sizes, but the local is newer -- are prompted for.
 | |
|   #
 | |
|   # Files are globbed on the remote host --- assuming, of course, they
 | |
|   # haven't already been globbed local, so use 'noglob' e.g. as
 | |
|   # `alias zfuget="noglob zfuget"'.
 | |
|   #
 | |
|   # Options:
 | |
|   #  -G    Glob:     turn off globbing
 | |
|   #  -v    verbose:  print more about the files listed.
 | |
|   #  -s    silent:   don't ask, just guess.  The guesses are:
 | |
|   #                - if the files have different sizes but remote is older ) grab
 | |
|   #                - if they have the same size but remote is newer        )
 | |
|   #                  which is safe if the remote files are always the right ones.
 | |
|   #   -t   time:     update the local file times to the same time as the remote.
 | |
|   #                  Currently this only works if you have the `perl' command,
 | |
|   #                  and that perl is version 5 with the standard library.
 | |
|   #                  See the function zfrtime for more gory details.
 | |
|   
 | |
|   setopt localoptions
 | |
|   unsetopt ksharrays shwordsplit
 | |
|   
 | |
|   local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuget$$
 | |
|   local rstat remlist verbose optlist opt bad i silent nglob time
 | |
|   integer stat do_close
 | |
|   
 | |
|   zfuget_print_time() {
 | |
|     local tim=$1
 | |
|     print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
 | |
|     print -n GMT
 | |
|   }
 | |
|   
 | |
|   zfuget_print () {
 | |
|     print -n "\nremote $rem ("
 | |
|     zfuget_print_time $remstats[2]
 | |
|     print -n ", $remstats[1] bytes)\nlocal $loc ("
 | |
|     zfuget_print_time $locstats[2]
 | |
|     print ", $locstats[1] bytes)"
 | |
|   }
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $optlist[$i] in
 | |
|         v) verbose=1
 | |
|   	 ;;
 | |
|         s) silent=1
 | |
|   	 ;;
 | |
|         G) nglob=1
 | |
|   	 ;;
 | |
|         t) time=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   [[ -n $bad ]] && return 1
 | |
|   
 | |
|   zfautocheck
 | |
|   
 | |
|   for remlist in $*; do
 | |
|     # zfcd directory hack to put the front back to ~
 | |
|     if [[ $remlist == $HOME || $remlist == $HOME/* ]]; then
 | |
|       remlist="~${remlist#$HOME}"
 | |
|     fi
 | |
|     if [[ $nglob != 1 ]]; then
 | |
|       zfrglob remlist
 | |
|     fi
 | |
|     if (( $#remlist )); then
 | |
|       for rem in $remlist; do
 | |
|         loc=${rem:t}
 | |
|         doit=y
 | |
|         remstats=()
 | |
|         if [[ -f $loc ]]; then
 | |
|   	zftp local $loc >$tmpfile
 | |
|   	locstats=($(<$tmpfile))
 | |
|   	zftp remote $rem >$tmpfile
 | |
|   	rstat=$?
 | |
|   	remstats=($(<$tmpfile))
 | |
|   	rm -f $tmpfile
 | |
|   	if [[ $rstat = 2 ]]; then
 | |
|   	  print "Server does not implement full command set required." 1>&2
 | |
|   	  return 1
 | |
|   	elif [[ $rstat = 1 ]]; then
 | |
|   	  print "File not found on server: $rem" 1>&2
 | |
|   	  stat=1
 | |
|   	  continue
 | |
|   	fi
 | |
|   	[[ $verbose = 1 ]] && zfuget_print
 | |
|   	if (( $locstats[1] != $remstats[1] )); then
 | |
|   	  # Files have different sizes
 | |
|   	  if [[ $locstats[2] > $remstats[2] && $silent != 1 ]]; then
 | |
|   	    [[ $verbose != 1 ]] && zfuget_print
 | |
|   	    print "Local file $loc more recent than remote," 1>&2
 | |
|   	    print -n "but sizes are different.  Transfer anyway [y/n]? " 1>&2
 | |
|   	    read -q doit
 | |
|   	  fi
 | |
|   	else
 | |
|   	  # Files have same size
 | |
|   	  if [[ $locstats[2] < $remstats[2] ]]; then
 | |
|   	    if [[ $silent != 1 ]]; then
 | |
|   	      [[ $verbose != 1 ]] && zfuget_print
 | |
|   	      print "Local file $loc has same size as remote," 1>&2
 | |
|   	      print -n "but local file is older. Transfer anyway [y/n]? " 1>&2
 | |
|   	      read -q doit
 | |
|   	    fi
 | |
|   	  else
 | |
|   	    # presumably same file, so don't get it.
 | |
|   	    [[ $verbose = 1 ]] && print Not transferring
 | |
|   	    doit=n
 | |
|   	  fi
 | |
|   	fi
 | |
|         else
 | |
|   	[[ $verbose = 1 ]] && print New file $loc
 | |
|         fi
 | |
|         if [[ $doit = y ]]; then
 | |
|   	if zftp get $rem >$loc; then
 | |
|   	  if [[ $time = 1 ]]; then
 | |
|   	    # if $remstats is set, it's second element is the remote time
 | |
|   	    zfrtime $loc $rem $remstats[2]
 | |
|   	  fi
 | |
|   	else
 | |
|   	  stat=$?
 | |
|   	fi
 | |
|   	
 | |
|         fi
 | |
|       done
 | |
|     fi
 | |
|   done
 | |
|   
 | |
|   (( do_close )) && zfclose
 | |
|   
 | |
|   return $stat
 | |
| }
 | |
| 
 | |
| function zfuput {
 | |
|   # Put a list of files from the server with update.
 | |
|   # See zfuget for details.
 | |
|   #
 | |
|   # Options:
 | |
|   #  -v    verbose:  print more about the files listed.
 | |
|   #  -s    silent:   don't ask, just guess.  The guesses are:
 | |
|   #                - if the files have different sizes but remote is older ) grab
 | |
|   #                - if they have the same size but remote is newer        )
 | |
|   #                  which is safe if the remote files are always the right ones.
 | |
|   
 | |
|   setopt localoptions
 | |
|   unsetopt ksharrays shwordsplit
 | |
|   
 | |
|   local loc rem locstats remstats doit tmpfile=${TMPPREFIX}zfuput$$
 | |
|   local rstat verbose optlist opt bad i silent
 | |
|   integer stat do_close
 | |
|   
 | |
|   zfuput_print_time() {
 | |
|     local tim=$1
 | |
|     print -n "$tim[1,4]/$tim[5,6]/$tim[7,8] $tim[9,10]:$tim[11,12].$tim[13,14]"
 | |
|     print -n GMT
 | |
|   }
 | |
|   
 | |
|   zfuput_print () {
 | |
|     print -n "\nremote $rem ("
 | |
|     zfuput_print_time $remstats[2]
 | |
|     print -n ", $remstats[1] bytes)\nlocal $loc ("
 | |
|     zfuput_print_time $locstats[2]
 | |
|     print ", $locstats[1] bytes)"
 | |
|   }
 | |
|   
 | |
|   while [[ $1 = -* ]]; do
 | |
|     if [[ $1 = - || $1 = -- ]]; then
 | |
|       shift;
 | |
|       break;
 | |
|     fi
 | |
|     optlist=${1#-}
 | |
|     for (( i = 1; i <= $#optlist; i++)); do
 | |
|       opt=$optlist[$i]
 | |
|       case $optlist[$i] in
 | |
|         v) verbose=1
 | |
|   	 ;;
 | |
|         s) silent=1
 | |
|   	 ;;
 | |
|         *) print option $opt not recognised >&2
 | |
|   	 ;;
 | |
|       esac
 | |
|     done
 | |
|     shift
 | |
|   done
 | |
|   
 | |
|   [[ -n $bad ]] && return 1
 | |
|   
 | |
|   zfautocheck
 | |
|   
 | |
|   if [[ $ZFTP_VERBOSE = *5* ]]; then
 | |
|     # should we turn it off locally?
 | |
|     print "Messages with code 550 are harmless." >&2
 | |
|   fi
 | |
|   
 | |
|   for loc in $*; do
 | |
|     rem=${loc:t}
 | |
|     doit=y
 | |
|     remstats=()
 | |
|     if [[ ! -f $loc ]]; then
 | |
|       print "$loc: file not found" >&2
 | |
|       stat=1
 | |
|       continue
 | |
|     fi
 | |
|     zftp local $loc >$tmpfile
 | |
|     locstats=($(<$tmpfile))
 | |
|     zftp remote $rem >$tmpfile
 | |
|     rstat=$?
 | |
|     remstats=($(<$tmpfile))
 | |
|     rm -f $tmpfile
 | |
|     if [[ $rstat = 2 ]]; then
 | |
|       print "Server does not implement full command set required." 1>&2
 | |
|       return 1
 | |
|     elif [[ $rstat = 1 ]]; then
 | |
|       [[ $verbose = 1 ]] && print New file $loc
 | |
|     else
 | |
|       [[ $verbose = 1 ]] && zfuput_print
 | |
|       if (( $locstats[1] != $remstats[1] )); then
 | |
|         # Files have different sizes
 | |
|         if [[ $locstats[2] < $remstats[2] && $silent != 1 ]]; then
 | |
|   	[[ $verbose != 1 ]] && zfuput_print
 | |
|   	print "Remote file $rem more recent than local," 1>&2
 | |
|   	print -n "but sizes are different.  Transfer anyway [y/n]? " 1>&2
 | |
|   	read -q doit
 | |
|         fi
 | |
|       else
 | |
|         # Files have same size
 | |
|         if [[ $locstats[2] > $remstats[2] ]]; then
 | |
|   	if [[ $silent != 1 ]]; then
 | |
|   	  [[ $verbose != 1 ]] && zfuput_print
 | |
|   	  print "Remote file $rem has same size as local," 1>&2
 | |
|   	  print -n "but remote file is older. Transfer anyway [y/n]? " 1>&2
 | |
|   	  read -q doit
 | |
|   	fi
 | |
|         else
 | |
|   	# presumably same file, so don't get it.
 | |
|   	[[ $verbose = 1 ]] && print Not transferring
 | |
|   	doit=n
 | |
|         fi
 | |
|       fi
 | |
|     fi
 | |
|     if [[ $doit = y ]]; then
 | |
|       zftp put $rem <$loc || stat=$?
 | |
|     fi
 | |
|   done
 | |
|   
 | |
|   (( do_close )) && zfclose
 | |
|   
 | |
|   return $stat
 | |
| }
 |