The FTP (File Transfer Protocol) is one of the oldest and most popular ways of transferring files from one computer to another over a TCP network such as the internet. It is a Client/Server based protocol based on cear-text authentication. To get a detailed idea of what FTP is and how it is used, refer to http://en.wikipedia.org/wiki/File_Transfer_Protocol
In .NET, we implement the FTP functionality using the System.Net namespace. This is a comprehensive namespace which encapsulates functionality to perform various network tasks. These include FTP related tasks like connecting to an FTP server and listing its directory.
In this article, we will learn how to create an FTP Client using the VB.NET language right from scratch.
The IDE I have used is Visual Studio 2008, though you can easily port it to the 2010 version. The complete source-code for this solution is available on Google Code. You can download it here:
http://code.google.com/p/ftpexplorer/downloads/detail?name=ftpexplorer_source_1.zip.
You can also translate it to C# (with a little bit of effort) since all .NET languages share the same FCL and CLI. There is a C# equivalent for each syntax.
To begin, create a VB solution of the type “Windows Forms Application”. Add a Form to it, then add the below controls to the Form:
A TreeView control – to browse the local file system
A ListView control – to browse the remote ftp structure
A TextBox and a Button to type Url and connect to the FTP server
Two Upload and Download buttons
A multi-line TextBox for logging details
A StatusStrip control with a label on it (Optional)
This will be your FTP client interface where you can do things like connecting to a remote ftp site using your username and password and start uploading/downloading files. The appearance is quite minimalist, and I have abstained from adding many advanced functions like using an SSL connection and applying cryptography, so as not to overwhelm the learners. Here is our little FTP Explorer in action:
The Upload button uploads the selected file on the local system to the remote directory that is currently selected. Similarly, selecting a file on the remote system and clicking the download button downloads the file to the selected local folder.
On the remote system, double-clicking a folder will navigate to that folder. The big multi-line textbox is for logging the ftp events.
On the coding front, I have abstracted the core FTP related functionality in a different class called FtpClient, so that the functionality could be reused or exposed through an API.
The local-system's explorer-tree on the top left side is just filled with the drive letters in the beginning. Later, as the user starts expanding the nodes, they are populated with the sub-folders and files.
Private Sub tvwLocal_AfterExpand(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) Handles tvwLocal.AfterExpand
On Error GoTo em
Dim current As TreeNode = e.Node
'Dim nextFile As String = Dir(current.Name & "\", FileAttribute.Directory)
Dim dirInfo As String() = Directory.GetDirectories(current.Name & "\")
current.Nodes.Clear()
'Do While (nextFile IsNot Nothing AndAlso nextFile.Length > 0)
For Each nextFile As String In dirInfo
Dim dirnode As New TreeNode
dirnode.Text = ExtractFileName(nextFile)
dirnode.Name = nextFile ' current.Name & "\" & nextFile
dirnode.ImageIndex = Images.CLOSED_FOLDER
dirnode.SelectedImageIndex = Images.CLOSED_FOLDER
dirnode.Nodes.Add("*DUMMY*")
'current.Nodes.Add(current.Name & "\" & nextFile, nextFile, Images.CLOSED_FOLDER, Images.CLOSED_FOLDER)
current.Nodes.Add(dirnode)
'Loop
Next
dirInfo = Directory.GetFiles(current.Name & "\")
'nextFile = Dir(current.Name & "\")
For Each nextFile As String In dirInfo
current.Nodes.Add(nextFile, ExtractFileName(nextFile), Images.FILE, Images.FILE)
Next
Exit Sub
em:
If Err.Number <> 52 And Err.Number <> 57 Then
MessageBox.Show(Err.Description)
End If
End Sub
As for the FTP handling, the class constructor takes three arguments: the FTP host, username and password. The most important method in this class is the ListDirectory(). This function sends a LIST request to the remote ftp server and gets the directory and files listing in the form of an ftp-string. It then creates an object of FtpDirectory class which is basically a generic list of FtpFileInfo objects. FtpFileInfo is an important object that reads the ftp-string using regular expressions and categorizes them into a file or a folder.
Public Function ListDirectory(Optional ByVal directory As String = "") As FtpDirectory
On Error GoTo em
Dim url As String
If directory.Length = 0 Then
Me.CurrentDirectory = "/"
'url = Hostname
ElseIf directory = "." Then
'No need to do anything
ElseIf directory = ".." Then
If CurrentDirectory <> "/" Then
'/pub/a
Dim index As Integer = Me.CurrentDirectory.LastIndexOf("/")
CurrentDirectory = CurrentDirectory.Substring(0, index)
If CurrentDirectory.Length = 0 Then CurrentDirectory = "/"
Else
RaiseEvent StatusChanged("Already in the root directory")
'url = Hostname & "/" & CurrentDirectory
End If
Else
Me.CurrentDirectory &= IIf(CurrentDirectory.Length = 1, "", "/") & directory
End If
url = GetCurrentUrl()
RaiseEvent StatusChanged("Logging in with user " & Username)
'return a simple list of filenames in directory
Dim ftp As Net.FtpWebRequest = GetRequest(url)
'Set request to do simple list
ftp.Method = Net.WebRequestMethods.Ftp.ListDirectoryDetails
Dim response As FtpWebResponse = ftp.GetResponse()
Dim sr As New StreamReader(response.GetResponseStream())
Dim str As String = sr.ReadToEnd()
'replace CRLF to CR, remove last instance
str = str.Replace(vbCrLf, vbCr).TrimEnd(Chr(13))
'split the string into a list
If Not Authenticated Then
Authenticated = True
RaiseEvent StatusChanged(response.WelcomeMessage)
RaiseEvent StatusChanged("Login successful...")
End If
RaiseEvent StatusChanged("Current directory is " & CurrentDirectory)
Return New FtpDirectory(str, url)
Exit Function
em:
MessageBox.Show(Err.Description)
End Function
The regular expressions are matched based on six different unix directory listing patterns that are most commonly used.
Public Enum DirectoryEntryTypes
File
Directory
End Enum
Sub New(ByVal line As String, ByVal path As String)
'parse line
Dim m As Match = GetMatchingRegex(line)
If m Is Nothing Then
'failed
Throw New ApplicationException("Unable to parse line: " & line)
Else
Me.FileName = m.Groups("name").Value
Me.Path = path
Me.Size = CLng(m.Groups("size").Value)
Me.Permission = m.Groups("permission").Value
Dim _dir As String = m.Groups("dir").Value
If (_dir <> "" And _dir <> "-") Then
Me.FileType = DirectoryEntryTypes.Directory
Else
Me.FileType = DirectoryEntryTypes.File
End If
Try
Me.FileDateTime = Date.Parse(m.Groups("timestamp").Value)
Catch ex As Exception
Me.FileDateTime = Nothing
End Try
End If
End Sub
Private Function GetMatchingRegex(ByVal line As String) As Match
Dim formats As String() = { _
"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\w+\s+\w+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{4})\s+(?<name>.+)", _
"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\d+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{4})\s+(?<name>.+)", _
"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\d+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{1,2}:\d{2})\s+(?<name>.+)", _
"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})\s+\d+\s+\w+\s+\w+\s+(?<size>\d+)\s+(?<timestamp>\w+\s+\d+\s+\d{1,2}:\d{2})\s+(?<name>.+)", _
"(?<dir>[\-d])(?<permission>([\-r][\-w][\-xs]){3})(\s+)(?<size>(\d+))(\s+)(?<ctbit>(\w+\s\w+))(\s+)(?<size2>(\d+))\s+(?<timestamp>\w+\s+\d+\s+\d{2}:\d{2})\s+(?<name>.+)", _
"(?<timestamp>\d{2}\-\d{2}\-\d{2}\s+\d{2}:\d{2}[Aa|Pp][mM])\s+(?<dir>\<\w+\>){0,1}(?<size>\d+){0,1}\s+(?<name>.+)"}Dim rx As Regex, m As Match
For i As Integer = 0 To formats.Length - 1
rx = New Regex(formats(i))
m = rx.Match(line)
If m.Success Then
Return m
End If
Next
End Function
For connecting to the remote ftp server, we use the FtpWebRequest class provided by the .NET framework class library through the System.Net namespace. The StatusChange() event of the FtpClient is used to get notifications in the main form where we can display or log this status on a multi-line text-box.
Private Sub client_StatusChanged(ByVal newStatus As String) Handles client.StatusChanged
txtLog.AppendText(newStatus & Environment.NewLine)
Application.DoEvents()
End Sub
The client object's ListDirectory() function is again called when the list view item is double-clicked or an upload is complete
Private Sub ListRemoteDirectory(ByVal directory As String)
lvwRemote.Items.Clear()
'Request = CType(FtpWebRequest.Create(txtUrl.Text), FtpWebRequest)
'Request.Credentials = New NetworkCredential(cred(0), cred(1))
RemoteDir = client.ListDirectory(directory)
LastDirectory = directory
lvwRemote.Items.Add("..", "..")
For Each item As FtpFileInfo In RemoteDir
If item.FileType = FtpFileInfo.DirectoryEntryTypes.Directory Then
lvwRemote.Items.Add(item.FileName, Images.CLOSED_FOLDER)
Else
lvwRemote.Items.Add(item.FileName, Images.FILE)
End If
Next
End Sub
Private Sub lvwRemote_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles lvwRemote.DoubleClick
If lvwRemote.SelectedItems.Count = 0 Then
Dim item As ListViewItem = lvwRemote.SelectedItems(0)
If item.ImageIndex = Images.FILE Then
ListRemoteDirectory(item.Text)
End If
End If
End Sub
The remaining tricks of the trade can be easily understood once you go through the code. For any doubts or queries please contact me.
..............................................................................................
EDIT: As of today (22-07-2012), I've re-created this project with the following improvements:
1. I've realized that the complexity involved in implementing the FTPWebRequest, deserves a modular approach. Hence, I've developed the FTP library as a class component (DLL) separate from the GUI component. This way, the library can be reused without the burden or overhead of a WinForm.
2. Multi-threading: .NET can certainly leverage the multi-threading capabilities of the newer dual-core processors. I've hence implemented multi-threading by running uploads/downloads on separate threadeds.
3. Event-based: I've included some more events such as uploadCompleted and downloadCompleted to facilitate the end-users of the library.
4. Finally, I've developed the newer version in the C# language, as I'm more used to it in recent years.
You can find the newer version in the codeplex repository here.
The update tutorial for C# is here.