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.