From e8eb43fc308acb3f1a8ebada7633c097e5050e46 Mon Sep 17 00:00:00 2001 From: Tanaka Akira Date: Sun, 25 Apr 1999 15:43:45 +0000 Subject: Initial revision --- Doc/Zsh/zftpsys.yo | 435 +++++++++++++++++++++++++++++++++++++++++++ Doc/zshzftpsys.yo | 0 Functions/Zftp/README | 4 + Functions/Zftp/zfanon | 70 +++++++ Functions/Zftp/zfautocheck | 33 ++++ Functions/Zftp/zfcd | 52 ++++++ Functions/Zftp/zfcd_match | 42 +++++ Functions/Zftp/zfcget | 87 +++++++++ Functions/Zftp/zfclose | 3 + Functions/Zftp/zfcput | 76 ++++++++ Functions/Zftp/zfdir | 99 ++++++++++ Functions/Zftp/zfgcp | 83 +++++++++ Functions/Zftp/zfget | 64 +++++++ Functions/Zftp/zfget_match | 27 +++ Functions/Zftp/zfhere | 5 + Functions/Zftp/zfinit | 28 +++ Functions/Zftp/zfls | 13 ++ Functions/Zftp/zfopen | 42 +++++ Functions/Zftp/zfparams | 12 ++ Functions/Zftp/zfpcp | 47 +++++ Functions/Zftp/zfput | 23 +++ Functions/Zftp/zfrglob | 70 +++++++ Functions/Zftp/zfrtime | 45 +++++ Functions/Zftp/zfstat | 89 +++++++++ Functions/Zftp/zftp_chpwd | 39 ++++ Functions/Zftp/zftp_progress | 18 ++ Functions/Zftp/zftype | 30 +++ Functions/Zftp/zfuget | 147 +++++++++++++++ Functions/Zftp/zfuput | 115 ++++++++++++ 29 files changed, 1798 insertions(+) create mode 100644 Doc/Zsh/zftpsys.yo create mode 100644 Doc/zshzftpsys.yo create mode 100644 Functions/Zftp/README create mode 100644 Functions/Zftp/zfanon create mode 100644 Functions/Zftp/zfautocheck create mode 100644 Functions/Zftp/zfcd create mode 100644 Functions/Zftp/zfcd_match create mode 100644 Functions/Zftp/zfcget create mode 100644 Functions/Zftp/zfclose create mode 100644 Functions/Zftp/zfcput create mode 100644 Functions/Zftp/zfdir create mode 100644 Functions/Zftp/zfgcp create mode 100644 Functions/Zftp/zfget create mode 100644 Functions/Zftp/zfget_match create mode 100644 Functions/Zftp/zfhere create mode 100644 Functions/Zftp/zfinit create mode 100644 Functions/Zftp/zfls create mode 100644 Functions/Zftp/zfopen create mode 100644 Functions/Zftp/zfparams create mode 100644 Functions/Zftp/zfpcp create mode 100644 Functions/Zftp/zfput create mode 100644 Functions/Zftp/zfrglob create mode 100644 Functions/Zftp/zfrtime create mode 100644 Functions/Zftp/zfstat create mode 100644 Functions/Zftp/zftp_chpwd create mode 100644 Functions/Zftp/zftp_progress create mode 100644 Functions/Zftp/zftype create mode 100644 Functions/Zftp/zfuget create mode 100644 Functions/Zftp/zfuput diff --git a/Doc/Zsh/zftpsys.yo b/Doc/Zsh/zftpsys.yo new file mode 100644 index 000000000..f20a0a6c3 --- /dev/null +++ b/Doc/Zsh/zftpsys.yo @@ -0,0 +1,435 @@ +texinode(Zftp Function System)()(Completion System)(Top) +chapter(Zftp Function System) +cindex(zftp, function system) +sect(Description) + +This describes the set of shell functions supplied with the source +distribution as an interface to the tt(zftp) builtin command, allowing you +to perform FTP operations from the shell command line or within functions +or scripts. The interface is similar to a traditional FTP client (e.g. the +manref(ftp)(1) command itself), but as it is entirely done within the shell +all the familar completion, editing and globbing features, and so on, are +present, and macros are particularly simple to write as they are just +ordinary shell functions. + +The prerequisite is that the tt(zftp) command, as described in +ifzman(\ +zmanref(zshmodules) +)\ +ifnzman(\ +noderef(The zftp Module) +), must be available in the +version of tt(zsh) installed at your site. If the shell is configured to +load new commands at run time, it probably is: typing tt(zmodload zftp) +will make sure (if that runs silently, it has worked). If this is not the +case, it is possible tt(zftp) was linked into the shell anyway: to test +this, type tt(which zftp) and if tt(zftp) is available you will get the +message tt(zftp: shell built-in command). + +Commands given directly with tt(zftp) builtin may be interspersed between +the functions in this suite; in a few cases, using tt(zftp) directly may +cause some of the status information stored in shell parameters to become +invalid. Note in particular the description of the variables +tt($ZFTP_TMOUT), tt($ZFTP_PREFS) and tt($ZFTP_VERBOSE) for tt(zftp). + +startmenu() +menu(Installation) +menu(Zftp Functions) +menu(Miscellaneous Features) +endmenu() + +texinode(Installation)(Zftp Functions)()(Zftp Function System) +sect(Installation) + +You should make sure all the functions from the tt(Functions/Zftp) +directory of the source distribution are available; they all begin with the +two letters tt(zf). They may already have been installed on your system; +otherwise, you will need to find them and copy them. The directory should +appear as one of the elements of the tt($fpath) array, and the functions +should be autoloaded. Finally, to initialise the use of the system you +need to call the tt(zfinit) function. The following code in your +tt(.zshrc) will arrange for this; assume the functions are stored in the +directory tt(~/myfns): + +tt(indent( +nofill(fpath=(~/myfns $fpath)) +nofill(autoload ~/myfns/zf*(:t)) +nofill(zfinit) +)) + +Note that tt(zfinit) assumes you are using the tt(zmodload) method to +load the tt(zftp) command. If it is already built into the shell, change +tt(zfinit) to tt(zfinit -n). + +texinode(Zftp Functions)(Miscellaneous Features)(Installation)(Zftp Function System) +sect(Functions) + +The sequence of operations in performing a file transfer is essentially the +same as that in a standard FTP client. + +subsect(Opening a connection) +startitem() +item(tt(zfparams [ var(host) [ var(user) [ var(password) ... ] ] ]))( +Set or show the parameters for a future tt(zfopen) with no arguments. If +no arguments are given, the current parameters are displayed (the password +will be shown as a line of asterisks). If a host is given, and either the +var(user) or var(password) is not, they will be prompted for; also, any +parameter given as `tt(?)' will be prompted for. + +As tt(zfopen) calls tt(zfparams) to store the parameters, this usually need +not be called directly. +) +item(tt(zfopen [ -1 ] [ var(host) [ var(user) [ var(password) [ var(account) ] ] ] ]))( +If var(host) is present, open a connection to that host under username +var(user) with password var(password) (and, on the rare occasions when it +is necessary account var(account)). If a necessary parameter is missing or +given as `tt(?)' it will be prompted for. If var(host) is not present, use +a previously stored set of parameters. + +If the command was successful, and the terminal is an tt(xterm), a summary +will appear in the title bar, giving the local tt(host:directory) and the +remote tt(host:directory); this is handled by the function tt(zftp_chpwd), +described below. + +Normally, the var(host), var(user) and var(password) are internally +recorded for later re-opening, either by a tt(zfopen) with no arguments, or +automatically (see below). With the option tt(-1), no information is +stored. +) +item(tt(zfanon [ -1 ] var(host)))( +Open a connection var(host) for anonymous FTP. The username used is +tt(anonymous). The password (which will be reported the first time) is +generated from var(user)tt(@)tt(host); this is then stored in the shell +parameter tt($EMAIL_ADDR) which can alternatively be set manually to a +suitable string. +) +enditem() + +subsect(Directory management) +startitem() +xitem(tt(zfcd [ var(dir) ])) +xitem(tt(zfcd -)) +item(tt(zfcd var(old) var(new)))( +Change the current directory on the remote server: this is implemented to +have many of the features of the shell builtin tt(cd). + +In the first form with var(dir) present, change to the directory var(dir). +The command tt(zfcd ..) is treated specially, so is guaranteed to work on +non-UNIX servers (note this is handled internall by tt(zftp)). If var(dir) +is omitted, has the effect of tt(zfcd ~). + +The second form changes to the directory previously current. + +The third form attempts to change the current directory by replacing the +first occurrence of the string var(old) with the string var(new) in the +current directory. + +Note that in this command, and indeed anywhere a remote filename is +expected, the string which on the local host corresponds to tt(~) is +converted back to a tt(~) before being passed to the remote machine. +This is convenient because of the way expansion is performed on the command +line before tt(zfcd) receives a string. For example, suppose the command +is tt(zfcd ~/foo). The shell will expand this to a full path as in tt(zfcd +/home/user2/pws/foo). At this stage, tt(zfcd) recognises the initial path +as tt(~), and the directory sent to the remote host is tt(~/foo), so that +the tt(~) will be expanded by the server to the correct remote host +directory. Other named directories of the form tt(~name) are not treated +in this fashion. +) +item(tt(zfhere))( +Change directory on the remote server to the one corresponding to the +current local directory, with special handling of tt(~) as in tt(zfcd). +For example, if the current local directory is tt(~/foo/bar), then +tt(zfhere) performs the effect of tt(zfcd ~/foo/bar). +) +item(tt(zfdir [ -rfd ] [ - ] [ var(dir-options) ] [ var(dir) ]))( +Produce a long directory listing. The arguments var(dir-options) and +var(dir) are passed directly to the server and their effect is +implementation dependent, but specifying a particular remote directory +var(dir) is usually possible. The output is passed through pager. + +The directory is usually cached for re-use. In fact, two caches are +maintained. One is for use when there is no var(dir-options) or var(dir), +i.e. a full listing of the current remote directory; it is flushed +when the current remote directory changes. The other is +kept for repeated use of tt(zfdir) with the same arguments; for example, +repeated use of tt(zfdir /pub/gnu) will only require the directory to be +retrieved on the first call. Alternatively, this cache can be re-viewed with +the tt(-r) option. As relative directories will confuse +tt(zfdir), the tt(-f) option can be used to force the cache to be flushed. +Also, the option tt(-d) will delete both caches without showing a directory +listing. +) +item(tt(zfls) [ var(ls-options) ] [ var(dir) ])( +List files on the remote server. With no arguments, this will produce a +simple list of file names for the current remote directory. Any arguments +are passed directory to the server. No pager and no caching is used. +) +enditem() + +subsect(Status commands) +startitem() +item(tt(zftype) [ var(type) ])( +With no arguments, show the type of data to be transferred, usually ASCII +or binary. With an argument, change the type: the types tt(A) or +tt(ASCII) for ASCII data and tt(B) or tt(BINARY), tt(I) or tt(IMAGE) for +binary data are understood case-insensitively. +) +item(tt(zfstat) [ -v ])( +Show the status of the current or last connection, as well as the status of +some of tt(zftp)'s status variables. With the tt(-v) option, a more +verbose listing is produced by querying the server for its version of +events, too. +) +enditem() + +subsect(Retrieving files) +The commands for retrieving files all take at least two options. tt(-G) +suppresses remote filename expansion which would otherwise be performed +(see below for a more detailed description of that). tt(-t) attempts +to set the modification time of the local file to that of the remote file: +this requires version 5 of tt(perl), see the description of the function +tt(zfrtime) below for more information. + +startitem() +item(tt(zfget [ -Gt ] var(file1) ...))( +Retrieve all the listed files var(file1) ... one at a time from the remote +server. If a file contains a `tt(/)', the full name is passed to the +remote server, but the file is stored locally under the name given by the +part after the final `tt(/)'. +) +item(tt(zfuget [ -Gvst ] var(file1) ...))( +As tt(zfget), but only retrieve files where the version on the remote +server is newer (has a later modification time), or where the local file +does not exist. If the remote file is older but the files have different +sizes, or if the sizes are the same but the remote file is newer, the user +will usually be queried. With the option tt(-s), the command runs silently +and will always retrieve the file in either of those two cases. With the +option tt(-v), the command prints more information about the files while it +is working out whether or not to transfer them. +) +item(tt(zfcget [ -Gt ] var(file1) ...))( +As tt(zfget), but if any of the local files exists, and is shorter than +the corresponding remote file, the command assumes that it is the result of +a partially completed transfer and attempts to transfer the rest of the +file. This is useful on a poor connection which keeps failing. + +Note that this requires a commonly implemented, but non-standard, version +of the FTP protocol, so is not guaranteed to work on all servers. +) +xitem(tt(zfgcp [ -Gt ] var(remote-file) var(local-file))) +item(tt(zfgcp [ -Gt ] var(rfile1) ... var(ldir)))( +This retrieves files from the remote server with arguments behaving +similarly to the tt(cp) command. + +In the first form, copy var(remote-file) from the server to the local file +var(local-file). + +In the second form, copy all the remote files var(rfile1) ... into the +local directory var(ldir) retaining the same basenames. This assumes UNIX +directory semantics. +) +enditem() + +subsect(Sending files) +startitem() +item(tt(zfput var(file1) ...))( +Send all the var(file1) ... given separately to the remote server. If a +filename contains a `tt(/)', the full filename is used locally to find the +file, but only the basename is used for the remote file name. +) +item(tt(zfuput [ -vs ] var(file1) ...))( +As tt(zfput), but only send files which are newer than their local +equivalents, or if the remote file does not exist. The logic is the same +as for tt(zfuget), but reversed between local and remote files. +) +item(tt(zfcput var(file1) ...))( +As tt(zfput), but if any remote file already exists and is shorter than the +local equivalent, assume it is the result of an incomplete transfer and +send the rest of the file to append to the existing part. As the FTP +append command is part of the standard set, this is in principle more +likely to work than tt(zfcget). +) +xitem(tt(zfpcp var(local-file) var(remote-file))) +item(tt(zfpcp var(lfile1) ... var(rdir)))( +This sends files to the remote server with arguments behaving similarly to +the tt(cp) command. + +With two arguments, copy var(local-file) to the server as +var(remote-file). + +With more than two arguments, copy all the local files var(lfile1) ... into +the existing remote directory var(rdir) retaining the same basenames. This +assumes UNIX directory semantics. + +A problem arises if you attempt to use tt(zfpcp) var(lfile1) var(rdir), +i.e. the second form of copying but with two arguments, as the command has +no simple way of knowing if var(rdir) corresponds to a directory or a +filename. It attempts to resolve this in various ways. First, if the +var(rdir) argument is tt(.) or tt(..) or ends in a slash, it is assumed to +be a directory. Secondly, if the operation of copying to a remote file in +the first form failed, and the remote server sends back the expected +failure code 553 and a reply including the string `tt(Is a directory)', +then tt(zfpcp) will retry using the second form. +) +enditem() + +subsect(Closing the connectino) +startitem() +item(tt(zfclose))( +Close the connection. +) +enditem() + +subsect(Other functions) +Mostly, these functions will not be called directly (apart from +tt(zfinit)), but are described here for completeness. You may wish to +alter tt(zftp_chpwd) and tt(zftp_progress), in particular. + +startitem() +item(tt(zfinit [ -n ]))( +As decribed above, this is used to initialise the zftp function system. +The tt(-n) option should be used if the zftp command is already built into +the shell. +) +item(tt(zfautocheck [ -dn ]))( +This function is called to implement automatic reopening behaviour, as +described in more detail below. The options must appear in the first +argument; tt(-n) prevents the command from changing to the old directory, +while tt(-d) prevents it from setting the variable tt(do_close), which it +otherwise does as a flag for automatically closing the connection after a +transfer. The host and directory for the last session are stored in the +variable tt($zflastsession), but the internal host/user/password parameters +must also be correctly set. +) +item(tt(zfcd_match var(prefix) var(suffix)))( +This performs matching for completion of remote directory names. If the +remote server is UNIX, it will attempt to persuade the server to list the +remote directory with subdirectories marked, which usually works but is not +guaranteed. On other hosts it simply calls tt(zfget_match) and hence +completes all files, not just directories. On some systems, directories +may not even look like filenames. +) +item(tt(zfget_match var(prefix) var(suffix)))( +This performs matching for completion of remote filenames. It caches files +for the current directory (only) in the shell parameter tt($zftp_fcache). +It is in the form to be called by the tt(-K) option of tt(compctl), but +also works when called from a widget-style completion function with +var(prefix) and var(suffix) set appropriately. +) +item(tt(zfrglob var(varname)))( +Perform remote globbing, as describes in more detail below. var(varname) +is the name of a variable containing the pattern to be expanded; if there +were any matches, the same variable will be set to the exanded set of +filenames on return. +) +item(tt(zfrtime var(lfile) var(rfile) [ var(time) ]))( +Set the local file var(lfile) to have the same modification time as the +remote file var(rfile), or the explicit time var(time) in FTP format +tt(CCYYMMDDhhmmSS) for the GMT timezone. + +Currently this requires tt(perl) version 5 to perform the conversion from +GMT to local time. This is unfortunately difficult to do using shell code +alone. +) +item(tt(zftp_chpwd))( +This function is called every time a connection is opened, or closed, or +the remote directory changes. This version alters the title bar of an +tt(xterm) or tt(sun-cmd) terminal emulator to reflect the local and remote +hostnames and current directories. It works best when combined with the +function tt(chpwd). In particular, a function of the form + +tt(indent( +nofill(chpwd() {) +nofill( if [[ -n $ZFTP_USER ]]; then) +nofill( zftp_chpwd) +nofill( else) +nofill( # usual chpwd e.g put host:directory in title bar) +nofill( fi) +nofill(}) +)) + +fits in well. +) +item(tt(zftp_progress))( +This function shows the status of the transfer as the percentage of the +total so far transferred. It will not write anything unless the output is +going to a terminal; however, if you transfer files in the background, you +should tt(unfunction) this first. (Background file transfers don't work on +all OSes.) Note also that if you alter it, any output em(must) be to +standard error, as standard output may be a file being received. +) +enditem() + +texinode(Miscellaneous Features)()(Zftp Functions)(Zftp Function System) +sect(Miscellaneous Features) + +subsect(Remote globbing) + +The commands for retrieving files usually perform filename expansion +(globbing) on their arguments; this can be turned off by passing the option +tt(-G) to each of the commands. Normally this operates by retrieving a +complete list of files for the directory in question, then matching these +locally against the pattern supplied. This has the advantage that the full +range of zsh patterns (respecting the setting of the option +tt(EXTENDED_GLOB)) can be used. However, it means that the directory part +of a filename will not be expanded and must be given exactly. If the +remote server does not support the UNIX directory semantics, directory +handling is problematic and it is recommended that globbing only be used +within the current directory. The list of files in the current directory, +if retrieved, will be cached, so that subsequent globs in the same +directory without an interventing tt(zfcd) are fast. + +If the variable tt($zfrglob) is set to a non-zero length, globbing is +instead performed on the remote host: the server is asked for a list of +matching files. This is highly dependent on how the server is implemented, +though typically UNIX servers will provide support for basic glob +patterns. This may in some cases be faster, as it avoids retrieving the +entire list of directory contents. + +subsect(Automatic and temporary reopening) + +As described for the tt(zfopen) command, a subsequent tt(zfopen) with no +parameters will reopen the connection to the last host (this includes +connections made with the tt(zfanon) command). Opened in this fashion, the +connection starts in the default remote directory and will remain open +until explicitly closed. + +Automatic re-opening is also available. If a connection is not currently +open and a command requiring a connection is given, the last connection is +implicitly reopened. In this case the directory which was current when the +connection was closed again becomes the current directory (unless, of +course, the command given changes it). Automatic reopening will also take +place if the connection was close by the remote server for whatever reason +(e.g. a timeout). It is not available if the tt(-1) option to tt(zfopen) +or tt(zfanon) was used. + +Furthermore, if the command issued is a file transfer, the connection will +be closed after the transfer is finished, hence providing a one-shot mode +for transfers. This does not apply to directory changing or listing +commands; for example a tt(zfdir) may reopen a connection but will leave it +open. Also, automatic closure will only ever happen in the same command as +automatic opening, i.e a tt(zfdir) directly followed by a tt(zfget) will +never close the connection automatically. + +Information about the previous connection is given by the tt(zfstat) +function. So, for example, if that reports: + +tt(indent( +nofill(Not connected.) +nofill(Last session: ftp.bar.com:/pub/textfiles) +)) + +then the command tt(zfget file.txt) will attempt to reopen a connection to +tt(ftp.bar.com), retrieve the file tt(/pub/textfiles/file.txt), and +immediately close the connection again. On the other hand, tt(zfcd ..) +will open the connection in the directory tt(/pub) and leave it open. + +subsect(Completion) + +Completion of remote files and directories is supported. The older, +tt(compctl)-style completion is defined when tt(zfinit) is called; support +for the new widget-based completion system is provided in the function +tt(Completion/Builtins/_zftp), which should be installed with the other +functions of the completion system and hence should automatically be +available. diff --git a/Doc/zshzftpsys.yo b/Doc/zshzftpsys.yo new file mode 100644 index 000000000..e69de29bb diff --git a/Functions/Zftp/README b/Functions/Zftp/README new file mode 100644 index 000000000..794bff292 --- /dev/null +++ b/Functions/Zftp/README @@ -0,0 +1,4 @@ +This directory contains a set of functions acting as a front end to the +zftp command, provided as an add-on module. They allow you to perform FTP +tasks from within the shell using as many of the shell's own facilities +as possible. For more information, see the zshzftpsys manual page. diff --git a/Functions/Zftp/zfanon b/Functions/Zftp/zfanon new file mode 100644 index 000000000..d8a9d06a3 --- /dev/null +++ b/Functions/Zftp/zfanon @@ -0,0 +1,70 @@ +# function zfanon { + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfautocheck b/Functions/Zftp/zfautocheck new file mode 100644 index 000000000..abb994061 --- /dev/null +++ b/Functions/Zftp/zfautocheck @@ -0,0 +1,33 @@ +# 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 +elif [[ -n $lastsession && $ZFTP_HOST = ${lastsession%%:*} ]]; then + zfcd ${lastsession#*:} +fi + +# } diff --git a/Functions/Zftp/zfcd b/Functions/Zftp/zfcd new file mode 100644 index 000000000..b726d9f55 --- /dev/null +++ b/Functions/Zftp/zfcd @@ -0,0 +1,52 @@ +# 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. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfcd_match b/Functions/Zftp/zfcd_match new file mode 100644 index 000000000..67e719888 --- /dev/null +++ b/Functions/Zftp/zfcd_match @@ -0,0 +1,42 @@ +# function zfcd_match { + +emulate -L zsh + +# 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 + +# } diff --git a/Functions/Zftp/zfcget b/Functions/Zftp/zfcget new file mode 100644 index 000000000..fd6accfed --- /dev/null +++ b/Functions/Zftp/zfcget @@ -0,0 +1,87 @@ +# 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. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfclose b/Functions/Zftp/zfclose new file mode 100644 index 000000000..fb49efd51 --- /dev/null +++ b/Functions/Zftp/zfclose @@ -0,0 +1,3 @@ +# function zfclose { +zftp close +# } diff --git a/Functions/Zftp/zfcput b/Functions/Zftp/zfcput new file mode 100644 index 000000000..fad5c3f86 --- /dev/null +++ b/Functions/Zftp/zfcput @@ -0,0 +1,76 @@ +# 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 +c' is used. +# It would be nice to find a way of doing this which works on all OS's. + +emulate -L zsh + +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 +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 +# } diff --git a/Functions/Zftp/zfdir b/Functions/Zftp/zfdir new file mode 100644 index 000000000..55befe000 --- /dev/null +++ b/Functions/Zftp/zfdir @@ -0,0 +1,99 @@ +# 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. + +emulate -L zsh +setopt extendedglob + +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 +# } diff --git a/Functions/Zftp/zfgcp b/Functions/Zftp/zfgcp new file mode 100644 index 000000000..26a08697d --- /dev/null +++ b/Functions/Zftp/zfgcp @@ -0,0 +1,83 @@ +# 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. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfget b/Functions/Zftp/zfget new file mode 100644 index 000000000..878a36346 --- /dev/null +++ b/Functions/Zftp/zfget @@ -0,0 +1,64 @@ +# 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. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfget_match b/Functions/Zftp/zfget_match new file mode 100644 index 000000000..677108ede --- /dev/null +++ b/Functions/Zftp/zfget_match @@ -0,0 +1,27 @@ +# function zfget_match { + +emulate -L zsh + +# 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 +# } diff --git a/Functions/Zftp/zfhere b/Functions/Zftp/zfhere new file mode 100644 index 000000000..43e599d3a --- /dev/null +++ b/Functions/Zftp/zfhere @@ -0,0 +1,5 @@ +# function zfhere { +# Change to the directory corresponding to $PWD on the server. +# See zfcd for how this works. +zfcd $PWD +# } diff --git a/Functions/Zftp/zfinit b/Functions/Zftp/zfinit new file mode 100644 index 000000000..be827c6ac --- /dev/null +++ b/Functions/Zftp/zfinit @@ -0,0 +1,28 @@ +[[ $1 = -n ]] || 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: this is unnecessary with +# widget-based completion and can be commented out. +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 diff --git a/Functions/Zftp/zfls b/Functions/Zftp/zfls new file mode 100644 index 000000000..e8d3cfb28 --- /dev/null +++ b/Functions/Zftp/zfls @@ -0,0 +1,13 @@ +# function zfls { + +emulate -L zsh + +# directory hack, see zfcd +if [[ $1 = $HOME || $1 = $HOME/* ]]; then + 1="~${1#$HOME}" +fi + +zfautocheck -d + +zftp ls $* +# } diff --git a/Functions/Zftp/zfopen b/Functions/Zftp/zfopen new file mode 100644 index 000000000..fa9b4f81d --- /dev/null +++ b/Functions/Zftp/zfopen @@ -0,0 +1,42 @@ +# 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. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfparams b/Functions/Zftp/zfparams new file mode 100644 index 000000000..5c5262c52 --- /dev/null +++ b/Functions/Zftp/zfparams @@ -0,0 +1,12 @@ +# function zfparams { + +emulate -L zsh + +# 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 $* +# } diff --git a/Functions/Zftp/zfpcp b/Functions/Zftp/zfpcp new file mode 100644 index 000000000..ddd570e59 --- /dev/null +++ b/Functions/Zftp/zfpcp @@ -0,0 +1,47 @@ +# function zfpcp { +# ZFTP put as copy: i.e. first arguments are remote, last is local. +# Currently only supports +# zfcp lfile rfile +# if there are two arguments, or the second one is . or .., or ends +# with a slash +# or +# zfcp lfile1 lfile2 lfile3 ... rdir +# if there are more than two (because otherwise it doesn't +# know if the last argument is a directory on the remote machine). +# However, if the remote machine plays ball by telling us `Is a directory' +# when we try to copy to a directory, zfpcp will then try to do the correct +# thing. + +emulate -L zsh + +local rem loc +integer stat do_close + +zfautocheck + +if [[ $# -gt 2 || $2 = (.|..) || $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 + [[ -n $dir && $dir != */ ]] || dir="$dir/" + for loc in $*; do + rem=$dir${loc:t} + zftp put $rem <$loc || stat=1 + done +else + zftp put $2 <$1 + stat=$? + if [[ stat -ne 0 && $ZFTP_CODE = 553 && $ZFTP_REPLY = *'Is a directory'* ]] + then + zftp put $2/$1:t <$1 + stat=$? + fi +fi + +(( $do_close )) && zfclose + +return $stat +# } diff --git a/Functions/Zftp/zfput b/Functions/Zftp/zfput new file mode 100644 index 000000000..0687163f0 --- /dev/null +++ b/Functions/Zftp/zfput @@ -0,0 +1,23 @@ +# function zfput { +# Simple put: dump every file under the same name, but stripping +# off any directory parts to get the remote filename (i.e. always +# goes into current remote directory). Use zfpcp to specify new +# file name or new directory at remote end. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfrglob b/Functions/Zftp/zfrglob new file mode 100644 index 000000000..f9d67b3f2 --- /dev/null +++ b/Functions/Zftp/zfrglob @@ -0,0 +1,70 @@ +# 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. + +emulate -L zsh +setopt extendedglob + +local pat dir nondir files i + +eval pat=\$$1 + +# Check if we really need to do anything. Look for standard +# globbing characters, and if we are +# using zsh for the actual pattern matching also look for +# extendedglob characters. +if [[ $pat != *[][*?]* && + ( -n $zfrglob || $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: + # ${...:/foo} deletes occurrences of foo matching a complete word, + # while the ^ inverts the sense so that anything not matching the + # pattern in $nondir is excluded. + eval "$1=(\${files:/^\${~nondir}})" +fi +# } diff --git a/Functions/Zftp/zfrtime b/Functions/Zftp/zfrtime new file mode 100644 index 000000000..f63ffe04b --- /dev/null +++ b/Functions/Zftp/zfrtime @@ -0,0 +1,45 @@ +# 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. + +emulate -L zsh + +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 + +# } diff --git a/Functions/Zftp/zfstat b/Functions/Zftp/zfstat new file mode 100644 index 000000000..0ca755d03 --- /dev/null +++ b/Functions/Zftp/zfstat @@ -0,0 +1,89 @@ +# 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 +# } diff --git a/Functions/Zftp/zftp_chpwd b/Functions/Zftp/zftp_chpwd new file mode 100644 index 000000000..0df199cfb --- /dev/null +++ b/Functions/Zftp/zftp_chpwd @@ -0,0 +1,39 @@ +# 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 +# } diff --git a/Functions/Zftp/zftp_progress b/Functions/Zftp/zftp_progress new file mode 100644 index 000000000..e2b5084c4 --- /dev/null +++ b/Functions/Zftp/zftp_progress @@ -0,0 +1,18 @@ +# 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 +# } diff --git a/Functions/Zftp/zftype b/Functions/Zftp/zftype new file mode 100644 index 000000000..c3b93b7a0 --- /dev/null +++ b/Functions/Zftp/zftype @@ -0,0 +1,30 @@ +# 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 == (#i)a(sc(ii|)|) ]]; then + type=A + elif [[ $1 == (#i)i(m(age|)|) || $1 == (#i)b(in(ary|)|) ]]; then + type=I + else + print "Type not recognised: $1" 2>&1 + return 1 + fi + zftp type $type +fi +# } diff --git a/Functions/Zftp/zfuget b/Functions/Zftp/zfuget new file mode 100644 index 000000000..482da42e9 --- /dev/null +++ b/Functions/Zftp/zfuget @@ -0,0 +1,147 @@ +# 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. + +emulate -L zsh + +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 +# } diff --git a/Functions/Zftp/zfuput b/Functions/Zftp/zfuput new file mode 100644 index 000000000..b54d0d0d4 --- /dev/null +++ b/Functions/Zftp/zfuput @@ -0,0 +1,115 @@ +# 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. + +emulate -L zsh + +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 rem in $*; do + loc=${rem: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 +# } -- cgit 1.4.1