Thursday, November 25, 2010 at 12:05 AM.
system.verbs.builtins.tcp.ftp.writeFile
on writeFile (adrconnectiontable, filetext, path) {
<<Upload a file via FTP.
<<Changes:
<<09/06/00; 2:35:03 PM by PBS
<<Fix for "dataport not defined" bug.
local (response);
local (adrlistens = @system.temp.ftpClientListens);
if not defined (adrlistens^) {
new (tabletype, adrlistens)};
local (adrdaemon = @tcp.ftp.daemon);
on fatal (errmsg) {
<<tcp.ftp.quit ()
<<delete (adrconnectiontable)
tcp.ftp.closeConnection (adrconnectiontable);
scriptError (errmsg)};
on sureDirectory (directory) {
if directory == adrconnectiontable^.currentHostDirectory { // we're already there!
return};
response = tcp.ftp.sendCommand (adrconnectiontable, "CWD " + directory); // can we get there now?
if not (response beginsWith "2") { // failure
local (i, level=string.countFields (directory, '/'));
tcp.ftp.sendCommand (adrconnectiontable, "CWD " + "/"); // start at root
adrconnectiontable^.currentHostDirectory = "";
for i = 1 to level {
local (tempdir = string.nthField (directory, '/', i));
if tempdir == "" { // this is the root; skip it
continue};
response = tcp.ftp.sendCommand (adrconnectiontable, "CWD " + tempdir);
if response beginsWith "550" {
response = tcp.ftp.sendCommand (adrconnectiontable, "MKD " + tempdir);
if response beginsWith "550" { // we can't make the directory, possibly permissions error
scriptError (response)};
tcp.ftp.sendCommand (adrconnectiontable, "CWD " + tempdir)};
adrconnectiontable^.currentHostDirectory = adrconnectiontable^.currentHostDirectory + "/" + tempdir}};
response = tcp.ftp.sendCommand (adrconnectiontable, "PWD");
adrconnectiontable^.currentHostDirectory = string.nthField (response, '"', 2)};
on openActiveStream () {
local {
port;
hi;
lo;
portString;
start = clock.now()};
loop {
if clock.now() > (start + 60) { // don't wait forever!
scriptError ("Timeout trying to find available data stream port")};
semaphore.lock (this, 3600);
port = random (1025, 1100); //should be legal range
if defined (adrlistens^.[port]) { //this port already in use
semaphore.unlock (this);
continue}
else {
new (tableType, @adrlistens^.[port]);
semaphore.unlock (this);
break}};
adrlistens^.[port].ready = false;
adrlistens^.[port].listenID = tcp.listenStream (port, 1, adrdaemon, port);
hi = port / 256;
lo = port % 256;
portString = string.replaceAll (tcp.addressdecode(tcp.myaddress()), '.', ',') + "," + string(hi) + "," + string(lo);
tcp.ftp.sendCommand (adrconnectiontable, "PORT " + portString);
return (port)};
on openPassiveStream (portstring) {
local {
ipAddr;
hi;
lo};
if portstring contains '(' { // port information is between matching parentheses
portstring = string.delete (portstring, 1, string.patternMatch ("(", portstring));
portstring = string.delete (portstring, string.patternMatch (')', portstring), infinity);
portstring = string.popLeading (portstring, '"');
portstring = string.popTrailing (portstring, '"')}
else { // brute force way, searching for commas
local (leftHalf);
local (firstComma);
firstComma = string.patternMatch (',', portstring); // find first comma
if firstComma == 0 { // oops! we're in big trouble!
scriptError ("Can't determine passive port number! " + portstring)};
leftHalf = string.mid (portstring, 1, firstComma-1); // the first number we want is at the end of leftHalf
for i = sizeOf (leftHalf) downTo 1 {
if not string.isnumeric (leftHalf[i]) { // we've found a non-numeric
break}};
portstring = string.delete (portstring, 1, i)}; // chop off all but number
ipAddr = string.nthField (portstring, ',', 1);
ipAddr = ipAddr + "." + string.nthField (portstring, ',', 2);
ipAddr = ipAddr + "." + string.nthField (portstring, ',', 3);
ipAddr = ipAddr + "." + string.nthField (portstring, ',', 4);
hi = number (string.nthField (portstring, ',', 5)) * 256;
lo = number (string.nthField (portstring, ',', 6));
return (tcp.openStream (tcp.addressEncode(ipAddr), hi+lo))};
on stopListen (port) {
tcp.closeListen (adrlistens^.[port].listenID);
delete (@adrlistens^.[port])};
on waitForListen (port, timeout=60) {
local (start = clock.now());
loop {
if clock.now() > start+timeout {
fatal ("Timeout waiting for FTP data connection")};
if adrlistens^.[port].ready {
local (temp);
temp = adrlistens^.[port].stream;
stopListen (port);
return (temp)};
tcp.ftp.yieldProcessor ()}};
local (filename);
bundle { //set filename, possibly modify the path
if path contains "/" {
filename = string.nthfield (path, "/", string.countfields (path, "/"));
path = string.mid (path, 1, sizeof (path) - sizeof (filename))}
else {
filename = path;
path = ""}};
sureDirectory (path);
loop { // I deal with the "426" error on Mac by re-sending the file
local (isActive=false);
local (dataStream, dataPort);
tcp.ftp.sendCommand (adrconnectiontable, "TYPE I");
response = tcp.ftp.sendCommand (adrconnectiontable,"PASV"); // try to use passive mode
if response beginsWith "2" {
dataStream = openPassiveStream (response)}
else { // no PASV support from host
dataStream = openActiveStream (); // returns immediately with listenRef
isActive = true};
loop { // we repeat this sequence if the host is not quite ready yet
response = tcp.ftp.sendCommand (adrconnectiontable, "STOR " + filename);
case true {
response beginsWith "425" {
<<clock.waitSeconds (2)
thread.sleepFor (2);
continue};
response beginsWith "5" {
tcp.ftp.sendCommand (adrconnectiontable, "ABOR"); // abort the stream attempt
if isActive {
dataStream = waitForListen (dataPort)};
try { tcp.closeStream (dataStream) };
fatal (response)}}
else {
break}};
if isActive {
dataStream = waitForListen (dataPort)};
if sizeOf (filetext) > 0 { // if any data left
tcp.writeStream (dataStream, filetext)};
tcp.closeStream (dataStream);
response = tcp.ftp.readResponse (adrconnectiontable);
if not (response beginsWith "2") { // anything other than 2xx is probably error
if response beginsWith "426" { // we're currently seeing this on Mac
continue}; // it's not fatal so we try again
fatal (response)}
else {
break}}}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.