Posted: 1st Mar 2024 4:15
Far from complete, but it shows it's possible. I think I'm gonna try to build a full GUI just for the sake of it. What the code below can do is provide a working framework for using FTP. It will connect, authenticate, establish the secondary data connection, then finally retrieve a directory listing of files. This uses passive mode (as opposed to active), which I chose to do so because it's simpler to implement and test because the client requests connection details from the server rather than telling the server how to connect to the client. Also, there is no TLS here, so passwords are sent in plain text. Because of that, I would not recommended using this in any production application where the connection relies on your own personal credentials as it's simple to intercept. So if anyone would like to attempt to implement TLS......
One last note, the server address has to be an IP and not "ftp.myserver.com".

Commands:
ftp_connect( ip, user, pass, logging[0,1] )
ftp_SetLogFile( path )
ftp_disconnect()
ftp_setLocalDir( path )
ftp_listener()
ftp_list()
ftp_getFileList()
ftp_sendFile( filename )
ftp_getFile( filename )
ftp_SetDir( path )



ftp.agc
+ Code Snippet
#CONSTANT FTP_STATE_LOGIN_USER 	1
#CONSTANT FTP_STATE_LOGIN_PASS 	2
#CONSTANT FTP_STATE_READY 		3
#CONSTANT FTP_STATE_GET_FILE	4
#CONSTANT FTP_STATE_SEND_FILE	5
#CONSTANT FTP_STATE_LIST		6
#CONSTANT FTP_STATE_CWD			7

Global private_months$ as string[12] = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]


Type FTP_File
	name 		as string
	date 		as string
	isDirectory as integer
	symlink		as string
	permissions as string
EndType


/* 
 * FTP operates over 2 sockets; a command socket and a data socket.
 * All commands are sent over the command socket and persists over
 * the duration of the session.  The data socket handles sending
 * and receiving data on each command request. A new data socket
 * is used for each file transfer and closed upon completion. You 
 * should not have more than 2 sockets open during an ftp session.
 * Because of this, a job queue is implemented.
 */
Type Queue_Object
	serverFile as string
	localFile  as string
	state	as integer
EndType



Type FTP_Handler
	user 	 		as string			
	pass 	 		as string			
	cmdConn	 		as integer			//
	dataConn 		as integer			//
	flag	 		as integer 			// current or last job type
	bytes	 		as integer[]		// temporarily stores bytes from command socket 
	grantedAccess 	as integer			// 1 if user was granted access to server
	await	 		as integer			// 1 while a job is being processed
	remoteFile 		as string			// a filename on the remote server
	localFile		as string			// a filename on local user system
	fileId			as integer			// file ID for reading and writing
	datalog			as string[]			// stores all messages on the command socket
	logMode			as integer			// 1 to write to datalog, 0 don't log anything
	jobQueue 		as Queue_Object[]	// FTP handles 1 connection per file at a time
	allDataReceived as integer			// 1 when all data has been read during a file download
	allDataSent		as integer			// 1 when all data has been sent during a file upload
	localDir		as string			// Local absolute path of current working directory
	sizeEst			as integer			// An estimate in total bytes of the file being downloaded
	bytesRead		as integer			// How many bytes have been read during current download (may be higher than sizeEst)
	`bytesSent		as integer			// Number of bytes sent
	files			as FTP_File[]		// Array of files, this is emptied and repopulated each time ftp_list() is called
	chunkLimit		as integer			// Default 5120. Increase for faster downloads. Too high may impact performance
	logPath			as string			// Absolute path of log file. If not set, log is not written and lost when app closes
	getFiles		as integer			// Ready to read file listing
EndType


/* The main ftp object */
global _ftp as FTP_Handler



/**
 * Absolute file path. If this is not set, a log
 * will not be written, even if log mode is on.
 */
function ftp_SetLogFile(path as string)
	_ftp.logPath = path
endfunction



/**
 * Initiates the connection sequence to the FTP server
 * Only IP address is acceptable, no domain (ftp.mysite.com)
 */
function ftp_connect(ip as string, user as string, pass as string, logMode as integer)
	`if _ftp.logMode = 1 then opentowrite("raw:D:\AGK\ftp\media\dump.txt", 1)
	_ftp.cmdConn = connectSocket(ip, 21, 3000)
	_ftp.user = user
	_ftp.pass = pass
	_ftp.chunkLimit = 5120
	_ftp.logMode = logMode
	
	ftp_setLocalDir(getCurrentDir())
	
endfunction



/**
 * Disconnect from FTP server properly
 * 
 */
function ftp_disconnect()
	
	private_ftp_SendString(_ftp.cmdConn, "QUIT")

	if _ftp.dataConn > 0
		if getSocketConnected(_ftp.dataConn) = 1 then deleteSocket(_ftp.dataConn)
		_ftp.dataConn = 0
	endif
	
	if _ftp.logMode = 1 and _ftp.logPath <> ""
		f = openToWrite("raw:"+_ftp.logPath)
		for i = 0 to _ftp.datalog.length
			writeLine(f, _ftp.datalog[i])
		next i
		closeFile(f)
	endif
	
endfunction




/**
 * Sets the local working directory. The path where downloaded
 * files are written.
 */
function ftp_setLocalDir(dir as string)
	FILE_SEPARATOR$ = "\"
	if findString(dir, "/") > 0 then FILE_SEPARATOR$ = "/"

	if mid(dir, len(dir), 1) <> "/" and mid(dir, len(dir), 1) <> "\" then dir = dir + FILE_SEPARATOR$

	_ftp.localDir = dir
endfunction


/**
 * 
 * 
 */
function ftp_listener()
	// data socket
	if _ftp.dataConn > 0
		if getSocketConnected(_ftp.dataConn) = 1
			
			/* Handles file downloads from server */
			if _ftp.flag = FTP_STATE_GET_FILE and _ftp.fileId > 0 // make sure output file is ready
				if GetSocketBytesAvailable(_ftp.dataConn) > 0
					
					chunk = 0
					while getSocketBytesAvailable(_ftp.dataConn) > 0 and chunk < _ftp.chunkLimit
					
						inc chunk
						inc _ftp.bytesRead
						b = getSocketByte(_ftp.dataConn)
						
						if _ftp.flag = FTP_STATE_GET_FILE
							writeByte(_ftp.fileId, b)
						endif
					
					endwhile
					
				else
				
					if _ftp.flag = FTP_STATE_GET_FILE and _ftp.allDataReceived = 1
						if _ftp.fileId > 0
							closeFile(_ftp.fileId)
							_ftp.fileId = 0
							_ftp.remoteFile  = ""
							_ftp.localFile   = ""
							_ftp.await 	   = 0
							_ftp.allDataReceived = 0
							private_ftp_resetDataSocket()
							timething2 = GetMilliseconds()
						endif
					endif
					
				endif
			endif // writeFile
			
			
			/* Handles file upload to server  */
			if _ftp.flag = FTP_STATE_SEND_FILE and _ftp.fileId > 0
				/*
				* todo: 
				*   Needs chunking implemented for 
				*   larger files
				*/
				while fileEOF(_ftp.fileId) = 0
					b = readByte(_ftp.fileId)
					sendSocketByte(_ftp.dataConn, b)
				endwhile
				flushSocket(_ftp.dataConn)
				closeFile(_ftp.fileId)
				_ftp.fileId  = 0
				_ftp.remoteFile  = ""
				_ftp.localFile   = ""
				_ftp.allDataSent = 1
				private_ftp_resetDataSocket()
			endif
			
			
			/* Retrieves data containing list of files */
			if _ftp.flag = FTP_STATE_LIST and _ftp.getFiles = 1
				
				// Read all bytes first because we need to look ahead for end of line
				local bytes as integer[]
				
				while getSocketBytesAvailable(_ftp.dataConn) > 0
					b = getSocketByte(_ftp.dataConn)
					bytes.insert(b)
				endwhile
				
				
				s$ = ""
				_ftp.files.length = -1
				for i = 0 to bytes.length-1
					
					if bytes[i] = 13 and bytes[i+1] = 10
						if _ftp.logMode > 0 then _ftp.datalog.insert(s$)
						_ftp.files.insert(private_ftp_ParseFileString(s$))
						
						s$ = "" : inc i
					else
						s$ = s$ + chr(bytes[i])
					endif
				next i
				
				_ftp.await = 0
				_ftp.getFiles = 0
				private_ftp_resetDataSocket()

				
			endif
			
		endif // if dataConn connected
	endif // if dataConn
	
	
	
		
	// listen for data from server
	if _ftp.cmdConn > 0
		if getSocketConnected(_ftp.cmdConn) = 1
			if GetSocketBytesAvailable(_ftp.cmdConn) > 0
				
				while getSocketBytesAvailable(_ftp.cmdConn)
					b = getSocketByte(_ftp.cmdConn)
					_ftp.bytes.insert(b)
					`if _ftp.logMode > 0 then writeByte(_ftp.logFile, b)
					
				endwhile
			endif
		endif
	endif
	
	
	
	// process the bytes
	if _ftp.bytes.length > 0
		start = 0
		s$ = ""
		for i = 0 to _ftp.bytes.length-1
			
			// Lines are terminated with 0d0a (carriage return followed by newline feed)
			if _ftp.bytes[i] = 13 and _ftp.bytes[i+1] = 10
				if _ftp.logMode > 0 then _ftp.datalog.insert(s$)
				
				code$ = trimString(left(s$, 4), " ")
				
				if code$ = "220"  // ready to send commands to server
					private_ftp_SendString(_ftp.cmdConn, "USER "+_ftp.user)
				endif
				if code$ = "331"  // username ok, need password
					private_ftp_SendString(_ftp.cmdConn, "PASS "+_ftp.pass)
				endif
				if code$ = "230" // you've been granted access
					_ftp.grantedAccess = 1
				endif
				if code$ = "530"  // access denied (not logged in, wrong password, etc...)
					
				endif
				if code$ = "425"  // no data connection
					
				endif
				if code$ = "221" // command connection disconnected (user logged out)
					if _ftp.cmdConn  > 0
						if getSocketConnected(_ftp.cmdConn)  = 1 then deleteSocket(_ftp.cmdConn)
						_ftp.cmdConn = 0
					endif
				endif
				if code$ = "227" // response to PASV
					
					// server is telling us where to connect for the data connection
					i1 = findString(s$, "(")+1
					i2 = findString(s$, ")")
					t$ = mid(s$, i1, i2-i1)
					data_ip$  = getStringToken2(t$, ',', 1)+"."+getStringToken2(t$, ',', 2)+"."+getStringToken2(t$, ',', 3)+"."+getStringToken2(t$, ',', 4)
					data_port = val(getStringToken2(t$, ',', 5))*256 + val(getStringToken2(t$, ',', 6))
					
					_ftp.dataConn = connectSocket(data_ip$, data_port, 3000)
					
					if _ftp.flag = FTP_STATE_LIST
						private_ftp_SendString(_ftp.cmdConn, "TYPE A") // ASCII-mode
					else
						private_ftp_SendString(_ftp.cmdConn, "TYPE I") // binary-mode
					endif
				endif
				if code$ = "200"
					if _ftp.flag = FTP_STATE_GET_FILE
						private_ftp_SendString(_ftp.cmdConn, "RETR "+_ftp.remoteFile)
					endif
					if _ftp.flag = FTP_STATE_SEND_FILE
						private_ftp_SendString(_ftp.cmdConn, "STOR "+_ftp.remoteFile)
					endif
					if _ftp.flag = FTP_STATE_LIST
						private_ftp_SendString(_ftp.cmdConn, "LIST")
					endif
				endif
				if code$ = "150" // data connection established
					
					if _ftp.flag = FTP_STATE_GET_FILE // getting file from server
						//_ftp.writeFile = openToWrite(_ftp.localFile, 0)
						_ftp.fileId = openToWrite(_ftp.localFile, 0)
						_ftp.bytesRead = 0
						
						if findString(s$,"download", 1, 4) > 0
							size$ = getStringToken2(s$, " ", 2)
							term$ = getStringToken2(s$, " ", 3)
							if term$ = "kbytes"
								_ftp.sizeEst = floor(valFloat(size$)) * 1024
							endif
						endif
					endif
					
					if _ftp.flag = FTP_STATE_SEND_FILE // sending file to server
						//_ftp.readFile = openToRead(_ftp.localFile)
						_ftp.fileId = openToRead(_ftp.localFile)
						_ftp.allDataSent = 0
					endif
					
					if _ftp.flag = FTP_STATE_LIST
						
						`private_ftp_SendString(_ftp.cmdConn, "LIST")
					endif
					
					
					
				endif
				if code$ = "226" // data connection closed
					
					if _ftp.flag = FTP_STATE_GET_FILE
						_ftp.allDataReceived = 1
					endif
					
					if _ftp.flag = FTP_STATE_SEND_FILE
						_ftp.await = 0
					endif
					
					if _ftp.flag = FTP_STATE_LIST
						_ftp.getFiles = 1
					endif
					
				endif
				if code$ = "250"  // file action completed ok
					_ftp.await = 0
				endif
				if code$ = "550" // file operation not performed
					_ftp.await = 0
					_ftp.flag = 0
				endif
				s$ = ""
				inc i
			else
				s$ = s$ + chr(_ftp.bytes[i])
			endif
		next i
		_ftp.bytes.length = -1
	endif
	
	
	
	
	if _ftp.jobQueue.length > -1
		if _ftp.grantedAccess = 1 and _ftp.await = 0
			
			if _ftp.jobQueue[0].state = FTP_STATE_GET_FILE
				_ftp.fileId = 0
				_ftp.remoteFile  = _ftp.jobQueue[0].serverFile
				_ftp.localFile   = _ftp.jobQueue[0].localFile
				_ftp.jobQueue.remove(0)
				_ftp.await    = 1  // ftp only likes one command at a time
				private_ftp_resetDataSocket() // free up any previous data socket
				_ftp.flag = FTP_STATE_GET_FILE // track what the ftp connection is currently doing
				private_ftp_SendString(_ftp.cmdConn, "PASV") // initiate new passive data socket
				
				
			elseif _ftp.jobQueue[0].state = FTP_STATE_SEND_FILE
			
				_ftp.remoteFile = _ftp.jobQueue[0].serverFile
				_ftp.localFile  = _ftp.jobQueue[0].localFile
				_ftp.jobQueue.remove(0)
				_ftp.fileId = 0
				_ftp.await    = 1
				private_ftp_resetDataSocket()
				_ftp.flag = FTP_STATE_SEND_FILE
				private_ftp_SendString(_ftp.cmdConn, "PASV")
				
			elseif _ftp.jobQueue[0].state = FTP_STATE_LIST
				_ftp.getFiles = 0
				_ftp.jobQueue.remove(0)
				_ftp.await = 1
				private_ftp_resetDataSocket()
				_ftp.flag = FTP_STATE_LIST
				private_ftp_SendString(_ftp.cmdConn, "PASV")
			elseif _ftp.jobQueue[0].state = FTP_STATE_CWD
				_ftp.await    = 1
				private_ftp_SendString(_ftp.cmdConn, "CWD "+_ftp.jobQueue[0].serverFile)
				_ftp.jobQueue.remove(0)
			endif
			
			
		endif
			
	endif
		
endfunction





/**
 * Requests a directory listing
 * 
 */
function ftp_list()
	local q as Queue_Object
	
	q.state = FTP_STATE_LIST
	_ftp.jobQueue.insert(q)

endfunction


/**
 * Returns an array of FTP_File
 * Array is empty until ftp_list() has been
 * called and completes processing
 */
function ftp_getFileList()
endfunction _ftp.files




/**
 * Just the file name, not an absolute path.
 * Path is determined by the currently set local dir
 */
function ftp_sendFile(localFilename as string)
	local q as Queue_Object
	
	q.state = FTP_STATE_SEND_FILE
	
	q.localFile = "raw:"+_ftp.localDir+localFilename
	q.serverFile = localFilename
	
	_ftp.jobQueue.insert(q)
	
endfunction




/**
 * Downloads a file from the server and writes
 * to local path as set by ftp_setLocalDir()
 */
function ftp_getFile(serverPath as string)
	local q as Queue_Object
	
	q.state = FTP_STATE_GET_FILE
	
	i = findStringReverse(serverPath, "\")
	if i = 0 then i = findStringReverse(serverPath, "/")
	filename$ = mid(serverPath, i+1, len(serverPath))

	
	q.serverFile = filename$
	q.localFile  = "raw:"+_ftp.localDir+filename$
	
	_ftp.jobQueue.insert(q)
	
endfunction



function ftp_SetDir(path as string)
	local q as Queue_Object
	q.state = FTP_STATE_CWD
	q.serverFile = path
	_ftp.jobQueue.insert(q)
endfunction



/**
 * Cleans up data socket
 * 
 */
function private_ftp_resetDataSocket()
	
	if _ftp.dataConn > 0
		if getSocketConnected(_ftp.dataConn) = 1 then deleteSocket(_ftp.dataConn)
		_ftp.dataConn = 0
	endif
	
endfunction




/**
 * Converts month abbreviation into number.
 * ie: 'Mar' = 3
 * 
 */
function private_ftp_GetMonth(m$)
	for i = 0 to private_months$.length
		if m$ = private_months$[i] then exitfunction i+1
	next i
endfunction 0



/**
 * Parses the response strings after requesting a list of files
 * from the current directory.
 *
 * Sample string may look something like this:
 * -rwx--x--x    5 ndc92f5    ndc92f5         34075 Dec  5  2022 test stuff.dat
 *
 */
function private_ftp_ParseFileString(s$ as string)
	local f as FTP_File

	p$ = getStringToken(s$, " ", 1)
	m$ = str(private_ftp_GetMonth(getStringToken(s$, " ", 6)))
	d$ = getStringToken(s$, " ", 7)
	y$ = getStringToken(s$, " ", 8)
	if findString(y$, ":") > 0 then y$ = str(getYearFromUnix(getUnixTime()))  // current year will show time instead of year

	f.date = m$+"/"+d$+"/"+y$

	i = findString(s$, getStringToken(s$, " ", 9))
	f.name = right(s$, len(s$)-i+1)


	c$ = mid(p$, 1, 1)
	if c$ = "l" // link
		i = findString(f.name, " ->")
		fname$ = left(f.name, i-1)
		f.symlink  = mid(f.name, i+4, len(f.name))
		f.name = fname$
		f.isDirectory = 1
	endif
	if c$ = "d" then f.isDirectory = 1

	o = 0
	g = 0
	e = 0
	if mid(s$, 2, 1)  = "r" then o = 4
	if mid(s$, 3, 1)  = "w" then o = o+2
	if mid(s$, 4, 1)  = "x" then o = o+1
	if mid(s$, 5, 1)  = "r" then g = 4
	if mid(s$, 6, 1)  = "w" then g = g+2
	if mid(s$, 7, 1)  = "x" then g = g+1
	if mid(s$, 8, 1)  = "r" then e = 4
	if mid(s$, 9, 1)  = "w" then e = e+2
	if mid(s$, 10, 1) = "x" then e = e+1

	f.permissions = str(o)+str(g)+str(e)

endfunction f




/**
 * 
 * 
 */
function private_ftp_SendString(c as integer, s as string)
	for i = 1 to len(s)
		sendSocketByte(c, asc(mid(s, i, 1)))
	next i
	sendSocketByte(c, 13)
	sendSocketByte(c, 10)
	flushSocket(c)
endfunction

Posted: 1st Mar 2024 22:42
I just added an FTP server to my virtual machine to test your code and i must say it works fine ^^
Are you working with github i'd like to add some easy commands like PWD to get the current deirectory or CWD to change the directory if you didn't add them already ?
Posted: 2nd Mar 2024 2:54
Great work, definitely of interest.
Posted: 2nd Mar 2024 18:04
I?ll add more commands in. I haven?t added it to GitHub but I can. I?ll want to restructure the code first, you can probably see why. It started as just a proof of concept. I got it to download an image file after having to put it in binary mode.
I?m actually surprised how hard it?s been to find decent documentation on the protocol.
Posted: 2nd Mar 2024 20:15
I like this one but it's mostly in German
https://www.elektronik-kompendium.de/sites/net/0902241.htm
Scroll down a bit and you'll find all the commands and satus codes.
Posted: 5th Mar 2024 9:49
Hi Phaelax,

I guesss this won't work on SFTP servers?

I know libCurlSSL does add this functionality, in comparison to libCurl.

Yeshu777
Posted: 5th Mar 2024 16:01
that is correct. I wouldn't say it's impossible to implement, but writing your own SSL library in tier 1 would be quite challenging.
Posted: 5th Mar 2024 20:23
Hi Phaelax,

In the end I just changed hosts to one that supported FTP also.. your code works a charm.

I had a play with changing directories, listing the contents..

How easy would it be to upload / download a simple "report.txt" file?

I've done all of this before using Visual Studio - albeit some years ago.

Might be able to create a Plugin with the C++ code I guess.

Yeshu777
Posted: 5th Mar 2024 23:33
Also, which you've probably already seen

FTP Commands:

https://en.wikipedia.org/wiki/List_of_FTP_commands

FTP Respsonse Codes:

https://en.wikipedia.org/wiki/List_of_FTP_server_return_codes

Yeshu777
Posted: 5th Mar 2024 23:48
I've been working on it, half the time figuring out how the sockets all worked the other with organizing everything into an easy library. I've updated my first post to the latest code and command set.
I haven't added directory changes yet, that'll be next. But you can upload and download files and get a directory listing. I've tested with a 120mb file.

Here's an example using the library above, even if somewhat of a sloppy presentation. The bandwidth calculation is only an estimate because of the file size it's based on. You can get a response like: "150 48.1 kbytes to download" when the exact size is 49,208. Plus, your frame rate also will affect this number :p


+ Code Snippet
// Project: ftp 
// Created: 2024-02-29
SetErrorMode(2)
SetWindowTitle( "ftp" )
SetWindowSize( 1280, 720, 0 )
SetVirtualResolution( 1280, 720 )
SetSyncRate(60, 0 )
UseNewDefaultFonts( 1 )
setPrintFont(loadFont("consola.ttf"))
setPrintSize(16)


#include 'ftp.agc'



ftp_SetLogFile("D:\AGK\ftp\media\ftp.log")

ftp_connect(serverIP$, user$, pass$, 1)



red = makecolor(255,0,0)

ftp_setLocalDir("D:\AGK\ftp\media\downloads\")


dx = getVirtualWidth() - 223
dy = 20

do

	print("Retrieving: "+str(_ftp.bytesRead) + " / " + str(_ftp.sizeEst)) // bytes received during last job

	b# = ((_ftp.bytesRead/1048576.0) / s#)*8

	print("Bandwidth: "+str(b#, 2)+" Mbps")  // estimated bandwidth

	// file download progress bar
	drawBox(dx, dy, dx+203, dy+30, red, red, red, red, 0)
	t = 200 * (_ftp.bytesRead / (_ftp.sizeEst+0.0))
	if t > 200 then t = 200
	if t < 0 then t = 0
	drawBox(dx+2, dy+2, dx+2+t, dy+28, red, red, red, red, 1)


	if timething2 = 0
		s# = (GetMilliseconds() - timething)/1000.0
	else
		s# = (timething2 - timething)/1000.0
	endif


	/*
	for i = 0 to _ftp.files.length
		print(_ftp.files[i].name)
	next i
	*/



	
	
	if getrawkeypressed(32) //space
		ftp_sendFile("sound.mp3")
		`ftp_list()
	endif
	
	if getrawkeypressed(13) // enter
		ftp_getFile("test 49.bmp")
		timething2 = 0
		timething = GetMilliseconds()
	endif
	
	
	// UP and DOWN to scroll the log
	if getRawKeyState(38) = 1
		dec page
		if page < 0 then page = 0
	endif
	if getRawKeyState(40) = 1
		inc page
	endif
	


	ftp_listener()
	

    // ESC to end session properly
	if getrawkeypressed(27)
		ftp_disconnect()
		exit
	endif 
    
    
    
    // Just to demonstrate commands are not blocking program flow
    inc a,2
    if a > 359 then a = 0
    x = cos(a)*50
    y = sin(a)*50
    drawLine(640,360,640+x,360+y,red, red)
    drawLine(640,360,640-x,360-y,red, red)
    
    
    
    
    // display the log
    print("")
    drawBox(0,50,500,546, red, red, red, red, 0)
    p = tmx_min(page+30, _ftp.datalog.length)
	for i = page to p
		print(_ftp.datalog[i])
	next i
	
	
    
    Sync()
loop




function tmx_min(n1, n2)
	if n1 > n2 then exitfunction n2
endfunction n1

Posted: 6th Mar 2024 0:37
well crap, I swear everything was working a moment ago, but now file listing isn't working...

*edit* ok, fixed it and added ftp_setDir()
Posted: 6th Mar 2024 19:20
Hey Phaelax,

This works brilliantly - great work!

Yeshu777
Posted: 14th Mar 2024 20:45
Hey Phaelax,

Created the following App using your ftp functions..



Yeshu777
Posted: 15th Mar 2024 21:40
You got that done quicker than I have! I'm working on a full client

Posted: 15th Mar 2024 22:06
Hey Phaelax,

Great work!!..

One problem I'm having is when the user or server timeout disconnects, I can't re-connect the socket.

The CONNECT button has your ftp_connect function attached to it, but doesn't seem to like the second attempt

See attached..

Any clues?

Yeshu777
Posted: 10th Apr 2024 19:17
Sounds like something needs to be reinitialized. The data connection is always recreated with each action, but the command connection stays open. If that gets disconnected from the other side, it would need to be recreated and it's likely I don't refresh something on multiple connection attempts. It's been a couple weeks since I've worked on this.