An FTP client for Tcl (RFC 959, with RFC 2640's utf-8 control channel),
with a timeout on every connect and read. Called from a coroutine it
yields while it waits, so other coroutines run; called outside one it
waits in the event loop (vwait).
Requires Tcl 8.6 or later, and the packages gc_class, parse_args, reuri 0.15+ and aio.
package require rl_ftp
rl_ftp::client create ftp -timeout 20 ftp://user:secret@ftp.example.com/incoming
try {
ftp upload photo.jpg $jpeg_bytes
set listing [ftp ls]
set bytes [ftp download orders.csv]
} finally {
ftp destroy
}rl_ftp::client is a gc_class, so
rl_ftp::client instvar ftp ... ties the client's life to the variable
ftp: it's closed when the variable is unset or goes out of scope.
Connects to the server named in url (ftp://[user[:password]@]host[:port][/path],
port 21 by default), logs in when the url has a user, and changes to path
(relative to the login directory, as one CWD). The user, password and
path are percent-decoded, so encode a :, @ or / in them. -acct
gives an account for servers that ask for one (ACCT). -timeout (10 s
by default, fractions allowed) bounds the connect and each wait for data.
Transfers use passive mode (PASV).
- upload path bytes: stores bytes at path, in binary (
TYPE I). - download path: returns the bytes of the file at path, in binary.
- ls ?pattern?: returns the server's
LISToutput, a list of lines.
The client has a method for each of RFC 959's other commands, named for the command in lower case. Each sends the command with its arguments and returns the server's reply text, or throws (below):
abor, allo, dele, cwd, cdup, smnt, help, mode, noop,
pasv, quit, site, port, syst, stat, rmd, mkd, pwd,
stru, type, feat, lang.
The commands that transfer data (appe, list, nlst, rein, retr,
stor, stou) take ?-tail args? and a script to run once the server is
ready for the transfer, and restart -marker m -cmd cmd -path path script
resumes one. rename from to renames a file, login user ?pass? ?acct?
logs in, feats returns the server's features (FEAT) and has_feat name tests for one. setting name value changes type, mode or
stru, sending the command only when the value changes.
To log the conversation with the server, override the method _log {lvl msg}
in a subclass.
Errors carry an errorCode starting RL FTP:
| errorCode | |
|---|---|
RL FTP URL |
the url isn't an ftp: url with a host, or doesn't parse |
RL FTP CONNECT message |
the connection failed (refused, no such host, ...) |
RL FTP TIMEOUT CONNECTION |
the connect took longer than -timeout (the client is destroyed) |
RL FTP TIMEOUT READ |
the server sent nothing for -timeout (the client is destroyed) |
RL FTP CONTROL_DROPPED |
the server closed the control connection |
RL FTP FAIL code |
the server refused the command (4xx and 5xx replies) |
RL FTP ERROR code |
a reply the command's state diagram doesn't allow there |
RL FTP PANIC ... |
a reply that makes no sense (an unknown code, an unreadable PASV address) |
RL FTP SYNTAX cmd |
restart was given a command other than APPE, STOR or RETR |
meson setup builddir
meson test -C builddir
meson install -C builddirmeson install puts rl_ftp-<version>.tm in Tcl's site-tcl module
directory, and rl_ftp.pc (for tcl_tm_path) in its pkgconfig. The
tests run against a fake FTP server in the test interp, so they need no
network beyond the loopback.
Copyright Ruby Lane, under a BSD-style license: see LICENSE.