I've written a simple, pure-J FTP class which encapsulates some of the
most used FTP commands.
I've tested it with FileZilla and ProFTP servers, it uses passive mode
to avoid NAT problems, hope it helps you.
Here is a file upload & download example:
require'files dir'
load 'C:\ftp.ijs'
HOST =: 'ip or host name'
USER =: 'username'
PASS =: 'password'
localdir =: jpath'~temp/'
fn =: 'test.txt'
'hello world' fwrite localdir,fn
11
ftp =: conew 'ftp'
connect__ftp HOST;USER;PASS
cd__ftp 'ftptest'
250 CWD command successful
lcd__ftp localdir
Local directory now C:\j64-602-user\temp\
NB. upload example:
ls__ftp''
. <dir> 2009 11 27 16 28 31
.. <dir> 2009 11 25 20 21 21
put__ftp fn
226 Transfer complete
ls__ftp'' NB.verify the file was written
. <dir> 2009 11 27 16 28 31
.. <dir> 2009 11 25 20 21 21
test.txt 11 2009 11 27 16 29 31
ferase localdir,fn
1
NB. download example:
dir localdir,'*'
jbreak <dir> 27-Nov-09 10:29:07
get__ftp fn
226 Transfer complete
dir localdir,'*' NB.verify the file was read
jbreak <dir> 27-Nov-09 10:29:07
test.txt 11 27-Nov-09 10:29:24
NB. Hosekeeping
ferase localdir,fn
1
delete__ftp fn
250 DELE command successful
disconnect__ftp''
221 Goodbye.
destroy__ftp''
1
Here is the class code:
--- ftp.ijs ---
NB. ftp class
0 : 0 NB. input lines for a sample session
require path-to-this-script
ftp =. conew 'ftp'
connect__ftp 'host';'user';'password'
ls__ftp ''
...
disconnect__ftp''
destroy__ftp''
)
require'socket'
coclass'ftp'
coinsert'jsocket'
NB. === Attributes =====================================================
NB.
FTP_PORT =: 21
TIMEOUT =: 3000
BUFFSIZE =: 5000
LF =: 10 { a.
LO =: _2
ER =: _1
host =: ''
connected =: 0
ctrlsk =: 0
datask =: 0
retmsg =: ''
ctrlbuff =: ''
databuff =: ''
fc =: 0
localdir =: ''
txmode =: 'ascii'
NB. === Initialize - Release ===========================================
NB.
create=: 3 : '(0 0$0)'
destroy=: 3 : 0
disconnect''
codestroy''
)
NB. === Exports ========================================================
NB.
NB. connect and log on to FTP server
connect=: 3 : 0
'ip user pswd' =. y
logonseq =. 0,LO,3, 1,LO,6, 2,LO,ER
acct =. ''
cmd =. ''
logonpoint =. 0
NB. convert name to x x x x
if. -.*./ip e. '.0123456789' do.
ip =. >2{sdgethostbyname_jsocket_ ip
end.
ctrlsk =: skclose ctrlsk
ctrlsk =: skopen ip;FTP_PORT
literal''
while. 1 do.
select. logonpoint { logonseq
case. 0 do.
cmd =. 'USER ',user
case. 1 do.
cmd =. 'PASS ',pswd
case. 2 do.
cmd =. 'ACCT ',acct
end.
if. -.ctrl_write cmd do. error'Write' end.
if. -.ctrl_read'' do. error'Read' end.
if. fc -...@e. 2 3 do. 0 return. end.
logonpoint =. (logonpoint+fc-1) { logonseq
select. logonpoint
case. ER do. error'Logon failed'
case. LO do. break.
end.
end.
host =: ip
connected =: 1
localdir =: ((,&PATHSEP_j_)@(1!:43))''
(0 0$0)
)
NB. log off & close connection to FTP server
disconnect =: 3 : 0
rslt =. (0 0$0)
if. ctrlsk do.
ctrl_write 'QUIT'
ctrl_read''
rslt=. retmsg
end.
ctrlsk=: skclose ctrlsk
datask=: skclose datask
host =: ''
connected =: 0
rslt
)
isConnected =: 3 : 0
connected
)
NB. Changes the working directory on the remote computer.
cd=: 3 : 0
literal 'CWD ',y
)
NB. Deletes a single file on a remote computer.
delete=: 3 : 0
literal 'DELE ', y
)
NB. Copies a remote file to the local computer using the current file
NB. transfer type.
get=: 3 : 0
'remote local' =. boxopen y
dat=. passive 'RETR ',remote
dat fwrite localdir,local
retmsg
)
NB. Changes the working directory on the local computer. By default, the
NB. working directory is the directory in which ftp was started.
lcd=: 3 : 0
ps =. PATHSEP_j_
if. 0<#y do.
if. '..'-: y do.
localdir =: (((}:localdir) >:@i: ps){.localdir)
elseif. y e. '/\' do.
localdir =: (localdir >:@i. ps) {. localdir
elseif. do.
localdir =: y
end.
end.
'Local directory now ', localdir
)
NB. Sends arguments, verbatim, to the remote FTP server.
literal=: 3 : 0
cmd =. y
if. (0~:#cmd) *. (-.ctrl_write cmd) do. error retmsg end.
if. (-.ctrl_read'') do. error retmsg end.
if. (fc~:2) do. error retmsg end.
retmsg
)
NB. Returns a list of a remote directory's files and subdirectories.
ls=: 3 : 0
dat=. passive 'MLSD'
if. 0=#dat do. dat return. end.
nms =. dss=. tms=. 0 0$0
for_tokens. }:<;._1@(';'&,);._1 LF,dat do.
nm =. }.>((<' ') i.~ {.&.> tokens) { tokens
type=. 5}.>((<'type=') i.~ 5&{.&.> tokens) { tokens
if. type-:'dir' do.
ds =. ' <dir> '
else.
ds =. 12":".5}.>((<'size=')i.~5&{.&.>tokens){tokens
end.
tm =.".7}.>((<'modify=') i.~ 7&{.&.> tokens) { tokens
nms =. nms,nm -. CRLF
dss =. dss,ds
tms =. tms,": 10000 100 100 100 100 100 #: tm
end.
nms,.dss,.' ',.tms
)
NB. Creates a remote directory.
mkdir=: 3 : 0
literal 'MKD ',y
)
NB. Copies a local file to the remote computer.
put=: 3 : 0
'local remote' =. boxopen y
dat=. fread (localdir,local)
if. dat=_1 do. error 'File not found: ',localdir,local end.
dat passive 'STOR ',remote
retmsg
)
NB. Sets or displays the file transfer type.
type=: 3 : 0
name=. tolower y
if. 0=#name do.
'Using ', txmode, ' to transfer files.' return.
elseif. name-:'ascii' do.
mode =. 'A'
elseif. name-:'binary' do.
mode =. 'I'
elseif. do.
error 'Invalid argument: ',y
end.
rslt =. literal 'TYPE ',mode
txmode =: name
rslt
)
NB. === Support ========================================================
NB.
error =: (13!:8)&7 NB. Throw 'Interface Error' with optional msg
skopen =: 3 : 0
'ip port' =. y
sk =. >0{sdcheck sdsocket''
sdcheck sdconnect sk;AF_INET;ip;port
sk
)
skclose =: 3 : 0
sk =. y
if. sk do. sdclose sk end.
0
)
NB. 1 if all data is written to the control channel, otherwise false
ctrl_write=: 3 : 0
ctrlsk write toHOST y,LF
)
write =: 4 : 0
sk =. x
dat =. y
if. -.sk e. >2{ sdselect '';sk;'';TIMEOUT do. 0 return. end.
while. #dat do.
'ec len'=.dat sdsend sk,0
er=. sderror ec
if. er-:'EWOULDBLOCK' do.
elseif. er-:'ECONNRESET' do. 0 return.
elseif. ec=0 do. dat=. len}.dat
elseif. 1 do. dat=.''
end.
end.
1
)
NB. return 1 if a line was read from the control channel, otherwise 0
ctrl_read =: 3 : 0
if. -.ctrlsk e.>1{sdselect ctrlsk;'';'';TIMEOUT do. 0 return. end.
if. -.ctrlsk recvline'' do. 0 return. end.
retcode =. {.0".>;:retmsg
if. (retcode=500)*.('creative'+./@:E. retmsg) do.
ctrl_read'' return. NB. Ignore "try being more creative" responses
end.
if. (4 > #retmsg) +. ('-' ~: 3{4{.retmsg) do. 1 return. end.
while. 1 do.
if. (3<#retmsg)*.(' '=3{4{.retmsg)*.(retcode={.0".>;:retmsg) do.
break.
end.
if. -.ctrlsk recvline'' do. 0 return. end.
end.
1
)
read =: 4 : 0
sk =. x
dat =. ''
while. 1 do.
c=. sk recvbytes ''
if. (0=#c) do. break. end.
dat=. dat, c
end.
dat
)
recvbytes=: 4 : 0
sk =. x
len =. >(y-:''){y;BUFFSIZE
dat =. ''
while. len~:#dat do.
NB. use a buffer to minimize calls to sdrecv
buff =. (sk=ctrlsk) >@{ databuff;ctrlbuff
if. #buff do.
ec =. 0
d =. (len <. #buff) {. buff
buff =. (len <. #buff) }. buff
else.
'ec d' =. sdrecv sk,BUFFSIZE
if. (#d) > len do.
buff =. buff, len }.d
d =. len {. d
end.
end.
if. sk=ctrlsk do.
ctrlbuff =: buff
else.
databuff =: buff
end.
er =. sderror ec
if. er-:'EWOULDBLOCK' do.
elseif. (er-:'ECONNRESET')+.(ec=0)*.0=#d do. '' return.
elseif. ec~:0 do. ''[retmsg=:er return.
elseif. 1 do.
dat=. dat,d
if. y-:'' do. len=.#dat end.
end.
end.
dat
)
recvline=: 4 : 0
sk =. x
retmsg =: ''
line =. ''
while. 1 do.
c=. sk recvbytes 1
if. (0=#c) +. (c=LF) do. break. end.
line=. line, c
end.
if. (#line) do. if. (_1{line)=CR do.
line =. }:line
end. end.
retmsg =: line
if. #line do.
fc =: (a.i. {.line) - 48
else.
fc =: 0
end.
1
)
passive=: 3 : 0
'' passive y
:
dat =. x
cmd =. y
literal 'PASV'
str =. retmsg
pos =. str i. '()'
if. (#str) e. pos do. error str end.
str =. ((>:@[ + ([: i. <:@(-~)))/ pos) { str
'ip port' =. (3 { str I.@:= ',') split str
ip =. ip rplc ',';'.'
port =. 256#.".port
ctrl_write cmd
datask =: skclose datask
try.
datask =: skopen ip;port
catch.
end.
if. -.ctrl_read'' do. error retmsg end.
if. fc ~: 1 do. error retmsg end.
sdcheck sdioctl datask,FIONBIO,1
if. #dat do.
datask write dat
else.
dat =. datask read ''
end.
datask =: skclose datask
literal''
dat
)
----------------
On Thu, Nov 26, 2009 at 8:44 AM, Ian Clark <[email protected]> wrote:
> I need a routine to upload a given file (or better: a J character
> string) to a remote FTP site. I don't need a top-end like FileZilla.
> The username, password, port, remote directory, filename can all be
> hard-coded or passed as parameters.
>
> I don't need a flexible FTP session, except maybe to debug it when
> working with a new site. It just needs to abort if the session goes
> wrong. It would be nice if it could download as well as upload,
> especially to download the directory of the remote target folder.
>
> I have just such a routine written in APL+Win which I've found quite
> satisfactory in use, but this only works in Windows. I want to port
> the APL+Win apps that make use of it to the Macintosh, for which I
> don't have development tools nowadays, so (presumably) it needs to be
> written in J.
>
> Ian Clark
> ----------------------------------------------------------------------
> For information about J forums see http://www.jsoftware.com/forums.htm
>
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm