This is the first thing i've ever done on BlitzMax. I've coded the same engine for B+ too and now I've done it for BlitzMax.
An easy way to read online/remote files. Supports FTP and HTTP protocols.
Functions:
Example:
Engine:
An easy way to read online/remote files. Supports FTP and HTTP protocols.
Functions:
file:TRemoteFile = OpenRemoteFile:TRemoteFile(url:String, port:Int = 80, HTTPpostdata/FTPusername, HTTPheader/FTPpassword) RemoteReadAvail:Int(file:TRemoteFile) ReadRemoteLine:String(file:TRemoteFile) ReadRemoteString:String(file:TRemoteFile, length:Int) ReadRemoteByte:Byte(file:TRemoteFile) ReadRemoteInt:Int(file:TRemoteFile) ReadRemoteLong:Long(file:TRemoteFile) ReadRemoteDouble:Double(file:TRemoteFile) ReadRemoteFloat:Float(file:TRemoteFile) ReadRemoteShort:Short(file:TRemoteFile) RemoteFileError:String(file:TRemoteFile) ' = "" if there was no error RemoteFileName:String(file:TRemoteFile) ' sometimes the URL doesn't contain the name of the file and it's specified in HTTP header RemoteFileSize:Int(file:TRemoteFile) ' -1 if there was no size sent by server EORF(file:TRemoteFile) ' End Of Remote File CloseRemoteFile(file:TRemoteFile)
Example:
Strict Include "transporter.bmx" 'Local file:TRemoteFile = OpenRemoteFile("ftp://www.google.com/", 21, "username", "password") Local file:TRemoteFile = OpenRemoteFile("http://www.google.com/") ' Check for errors If RemoteFileError(file) = "" 'Wait until there's something to read While Not RemoteReadAvail(file) Wend ' Read until the end of the file/stream While Not EORF(file) Print RemoteFileName(file) + " [" + RemoteReadAvail(file) + "] "+ " -> " + ReadRemoteLine(file) Wend ' Close file/stream CloseRemoteFile(file) Else Notify RemoteFileError(file) EndIf End
Engine:
Const RemoteFileCRLF:String = Chr(13) + Chr(10) Const RemoteFileUserAgent:String = "Transporter" Const RemoteFileTimeOut:Int = 5 * 1000 Const RemoteFileDebugMode:Int = True Type TRemoteFile Field url:String, port:Int, protocol:String Field filename:String, size:Int Field socket:TSocket, stream:TSocketStream Field pasv_socket:TSocket, pasv_stream:TSocketStream Field error:String Function Open:TRemoteFile(url:String, port:Int = 80, postdata:String = "", header:String = "") Local file:TRemoteFile = New TRemoteFile Local protocol:String = Sector(url, ":", 0) Local host:String = Sector(url, "/", 2) Local path:String = "/" + Sector(url, "/", 3, True) ' Check for port in server name If Instr(host, ":") Then port = Int(Sector(host, ":", 1)) host = Sector(host, ":", 0) EndIf Local server:String = "www." + Sector$(host$, ".", Sectors%(host$, ".") - 1) + "." + Sector$(host$, ".", Sectors%(host$, ".")) file.url = url file.port = port file.protocol = Lower(protocol) file.error = "" file.filename = Sector(path, "/", Sectors(path, "/")) file.size = -1 file.socket = CreateTCPSocket:TSocket() BindSocket(file.socket, 0) ' Connect to server Local connection:Int = ConnectSocket(file.socket, HostIp(server), port) If Not connection Then connection:Int = ConnectSocket(file.socket, HostIp(host), port) If connection file.stream = CreateSocketStream:TSocketStream(file.socket) Select Lower(protocol) Case "http" Local txt:String = "" If Len(postdata) > 0 Then txt :+ "POST " + path + " HTTP/1.1" + RemoteFileCRLF Else txt :+ "GET " + path + " HTTP/1.1" + RemoteFileCRLF txt :+ "Host: " + host + RemoteFileCRLF txt :+ "User-Agent: " + RemoteFileUserAgent + RemoteFileCRLF If Len(header) > 0 Then txt :+ header + RemoteFileCRLF If Len(postdata) > 0 Then txt :+ "Content-Type: application/x-www-form-urlencoded" + RemoteFileCRLF + "Content-Length: " + Len(postdata) + RemoteFileCRLF txt :+ "Connection: Close" + RemoteFileCRLF txt :+ "" If Len(postdata) > 0 Then txt :+ RemoteFileCRLF + postdata WriteLine(file.stream, txt) If RemoteFileDebugMode Then Print(txt) ' Check response Local tim:Int = MilliSecs() Repeat If RemoteReadAvail(file) txt = ReadRemoteLine(file) If Sector(txt, "/", 0) = "HTTP" If Int(Sector(txt, " ", 1)) => 303 Then file.error = "HTTP Error: " + txt Exit EndIf EndIf If (MilliSecs() - tim) => RemoteFileTimeOut Then file.error = "No response from server!" Forever Local cmd:String, value:String If file.error = "" Repeat If RemoteReadAvail(file) txt = ReadRemoteLine(file) If txt$ = "" Then Exit cmd = Sector(txt, ":", 0) value = Sector(txt, " ", 1) Select cmd Case "" Return file Case "Content-Disposition" Local i:Int, subtxt:String, subvalue:String For i = 0 To Sectors(value, ";") subtxt = Trim(Sector(value, ";", i)) subvalue = Sector(subtxt, "=", 1) Select Lower(Sector(subtxt, "=", 0)) Case "filename" file.filename = subvalue End Select Next Case "Content-Length" file.size = Int(value$) End Select EndIf Forever EndIf Case "ftp" ' Check response If RemoteFileDebugMode Then Print("Waiting FTP response...") Local tim:Int = MilliSecs() Repeat Until (MilliSecs() - tim) => RemoteFileTimeOut Or SocketReadAvail(file.socket) If RemoteFileDebugMode Then Print("Initializing PASV mode...") If SocketReadAvail(file.socket) Local txt:String, cmd:String, code:Int, value:String Local pasv_host:String, pasv_port:Int, pasv_connection:Int Repeat If SocketReadAvail(file.socket) txt = ReadLine(file.stream) If RemoteFileDebugMode Then Print("<<< " + txt) code = Int(Sector(txt, " ", 0)) value = Mid$(Sector(txt, " ", 1, True), 1) Else txt = "" code = 0 value = "" cmd = "" EndIf If Not SocketReadAvail(file.socket) Select code Case 220 cmd = "USER " + postdata Case 331 cmd = "PASS " + header Case 230 cmd = "SIZE " + path Case 213 file.size = Int(value) cmd = "PASV" Case 227 value = Sector(value, "(", 1) pasv_host = Sector(value, ",", 0) + "." + Sector(value, ",", 1) + "." + Sector(value, ",", 2) + "." + Sector(value, ",", 3) pasv_port = Int(Sector(value, ",", 4)) * 256 + Int(Left$(Sector(value, ",", 5), Len(Sector(value, ",", 5)) - 1)) file.pasv_socket = CreateTCPSocket() BindSocket(file.pasv_socket, 0) pasv_connection = ConnectSocket(file.pasv_socket, HostIp(pasv_host), pasv_port) If pasv_connection If RemoteFileDebugMode Then Print("PASV connection established") file.pasv_stream = CreateSocketStream(file.pasv_socket) cmd = "RETR " + path Else file.error = "Unable to connect to PASV stream on " + pasv_host + ":" + pasv_port EndIf Case 150 Return file End Select ' ERROR If code => 400 file.error = "FTP error: " + txt Return file EndIf EndIf If Len(cmd) Then If RemoteFileDebugMode Then Print(">>> " + cmd) WriteLine(file.stream, cmd) cmd = "" EndIf Forever Else file.error = "No response from " + file.url + ":" + file.port EndIf End Select Else file.error = "Unable to connect to " + file.url + ":" + file.port EndIf Return file End Function Method ProtocolStream:TSocketStream() Local str:TSocketStream Select Lower(protocol) Case "ftp" Return pasv_stream Default Return stream End Select End Method Method ProtocolSocket:TSocket() Local sock:TSocket Select Lower(protocol) Case "ftp" Return pasv_socket Default Return socket End Select End Method Method ReadAvail:Int() Return SocketReadAvail(ProtocolSocket()) End Method Method RemoteLine:String() Return ReadLine(ProtocolStream()) End Method Method RemoteString:String(length:Int) Return ReadString(ProtocolStream(), length) End Method Method RemoteByte:Byte() Return ReadByte(ProtocolStream()) End Method Method RemoteInt:Int() Return ReadInt(ProtocolStream()) End Method Method RemoteLong:Long() Return ReadLong(ProtocolStream()) End Method Method RemoteDouble:Double() Return ReadDouble(ProtocolStream()) End Method Method RemoteFloat:Float() Return ReadFloat(ProtocolStream()) End Method Method RemoteShort:Short() Return ReadShort(ProtocolStream()) End Method Method LastError:String() Return error End Method Method Name:String() Return filename End Method Method FSize:Int() Return size End Method Method EndOFRemoteFile() Return Eof(ProtocolStream()) End Method Method Close() Select Lower(protocol) Case "ftp" CloseStream(pasv_stream) CloseSocket(pasv_socket) End Select CloseStream(stream) CloseSocket(socket) End Method End Type Function OpenRemoteFile:TRemoteFile(url:String, port:Int = 80, postdata:String = "", header:String = "") Return TRemoteFile.Open(url, port, postdata, header) End Function Function RemoteReadAvail:Int(file:TRemoteFile) Return file.ReadAvail() End Function Function ReadRemoteLine:String(file:TRemoteFile) Return file.RemoteLine() End Function Function ReadRemoteString:String(file:TRemoteFile, length:Int) Return file.RemoteString(length) End Function Function ReadRemoteByte:Byte(file:TRemoteFile) Return file.RemoteByte() End Function Function ReadRemoteInt:Int(file:TRemoteFile) Return file.RemoteInt() End Function Function ReadRemoteLong:Long(file:TRemoteFile) Return file.RemoteLong() End Function Function ReadRemoteDouble:Double(file:TRemoteFile) Return file.RemoteDouble() End Function Function ReadRemoteFloat:Float(file:TRemoteFile) Return file.RemoteFloat() End Function Function ReadRemoteShort:Short(file:TRemoteFile) Return file.RemoteShort() End Function Function RemoteFileError:String(file:TRemoteFile) Return file.LastError() End Function Function RemoteFileName:String(file:TRemoteFile) Return file.Name() End Function Function RemoteFileSize:Int(file:TRemoteFile) Return file.FSize() End Function Function EORF(file:TRemoteFile) Return file.EndOFRemoteFile() End Function Function CloseRemoteFile(file:TRemoteFile) file.Close() End Function Function Sector:String(txt:String, separator:String, sector:Int, toend:Int = 0) Local result:String = "", occ:Int, i:Int For i:Int = 1 To Len(txt) If Mid(txt, i, 1) = separator occ :+ 1 If toend And occ > sector Then result = result + Mid(txt, i, 1) Else If occ => sector Then result = result + Mid(txt, i, 1) EndIf If Not toend Then If occ > sector Then Exit Next Return result End Function Function Sectors:Int(txt:String, needle:String) Local occ:Int = 0, i:Int For i:Int = 1 To Len(txt) If Instr(txt, needle, i) occ = occ + 1 i = Instr(txt, needle, i) Else Exit EndIf Next Return occ End Function