|
The project for this chapter is an FTP client application with a graphical user interface, as shown in Figure 5-2. This section starts with a general overview of the application. It then describes each class in detail.
Figure 5-2: The FTP client application
To get a feel for the application, you are encouraged to try this application by double-clicking the Form1.exe file in the listings/Ch05/Project directory.
When the application activates, it retrieves the content of the local current directory and displays it on the left panel of the form.
Before you can do a file transfer, you need to connect to an FTP server. You can connect and log in at the same time by pressing F3 or selecting File Connect. To log in, you type your login details in the Login Form window, as shown in Figure 5-3.
Figure 5-3: The Login Form window
You need to enter the server, the username, and the password into the Login Form window and then click the OK button.
If the connection is successful, the content of the remote home directory displays in the right panel of the form. If the login fails, the Login Form window remains open until you enter the correct login details or until you click the Cancel button.
The four buttons to the right of the left panel are for manipulating local files and directories, and the buttons to the right of the right panel are for manipulating remote files and directories. You can change the local or remote directory by double-clicking the directory icon on both panels.
To upload a file, you can select a file from the local computer and click the Upload button. Alternatively, you can simply double-click the file icon on the left panel.
To download a file, select a file from the remote computer and then click the Download button. Or, you can double-click the file icon.
Figure 5-4 shows the class diagram for this application.
Figure 5-4: The class diagram
The application is comprised of the following classes:
FTP: Contains properties and methods to send FTP commands to an FTP server.
Form1: The main form of the application.
Helper: Contains static methods used by the Form1 class
LoginForm: Represents a form for the user to log in.
Three EventArgs subclasses: EndDownloadEventArgs, EndUploadEventArgs, and TransferProgressChangedEventArgs. These are event argument classes used in several delegates in the FTP class.
You will now learn about the classes starting with the easiest.
You can find the Helper class in the Helper.vb file under the project's directory. It provides two static methods used by the Form1 class: IsDirectory and IsDirectoryItem.
The following sections describe the two methods of the Helper class.
The IsDirectory method accepts a string argument and returns True if the specified string is a path to a directory.
The method definition is as follows:
Public Shared Function IsDirectory(ByVal path As String) As Boolean If File.Exists(path) Or Directory.Exists(path) Then ' it is a file or a directory Dim attr As FileAttributes = File.GetAttributes(path) If (attr And FileAttributes.Directory) = FileAttributes.Directory Then Return True End If End If End Function
The IsDirectoryItem method accepts a System.Windows.Form.ListViewItem and returns True if the item's ImageIndex is 1, the image index of the folder icon. Its definition is as follows:
Public Shared Function IsDirectoryItem(ByVal item As ListViewItem) As Boolean If item.ImageIndex = 1 Then Return True Else Return False End If End Function
You can find the LoginForm class in the LoginForm.vb file in the project's directory. It represents the login form for the user to login. This form displays as a modal dialog box from the Form1 class when the user attempts to connect to a remote server.
The class contains three Label controls (label1, label2, and label3), three TextBox controls (serverTextBox, userTextBox, and passwordTextBox), and two Button controls (okButton and cnlButton).
The passwordTextBox control accepts the user's password, and the characters entered are masked by setting its PasswordChar property as follows:
Me.passwordTextBox.PasswordChar = Microsoft.VisualBasic.ChrW(42)
You set the okButton control's DialogResult property to System.Windows.Forms.DialogResult.OK so that when the form is shown as a modal dialog box, it returns DialogResult.OK when the okButton control is clicked:
Me.okButton.DialogResult = System.Windows.Forms.DialogResult.OK
On the other hand, you set the cnlButton.DialogResult property to System.Windows.Forms.DialogResult.Cancel. When the form displays as a modal dialog box, clicking the cnlButton control results in the form returning Dialog.Cancel:
Me.cnlButton.DialogResult = System.Windows.Forms.DialogResult.Cancel
The okButton control's Click event is wired with the okButton_Click event handler. This event handler populates three private fields with the values entered by the user into the serverTextBox, userTextBox, and passwordTextBox controls:
Private Sub okButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles okButton.Click userNameField = userTextBox.Text passwordField = passwordTextBox.Text serverField = serverTextBox.Text Me.Close() End Sub
The last line of this event handler also closes the form.
After the form returns—in other words, when either okButton or cnlButton is clicked—you can obtain the values of serverField, userNameField, and passwordField from the three read-only properties: Server, UserName, and Password:
Public ReadOnly Property Server() As String Get Return serverField End Get End Property Public ReadOnly Property UserName() As String Get Return userNameField End Get End Property Public ReadOnly Property Password() As String Get Return passwordField End Get End Property
You can find the FTP class in the FTP.vb file in the project's directory. This class encapsulates functions to communicate with an FTP server. Using this class, you can connect to a remote FTP server, log in, print the working directory, change the directory, delete and rename a remote file, and download and upload a file.
Some of the functions in the FTP class are similar to those in the NETFTP class discussed previously. However, you use a separate thread for downloading and uploading a file to improve the perceived performance. For synchronization, a Boolean called transferring prevents multiple upload/download at the same time. The Upload and Download methods return immediately if the value of transferring is True.
Finally, the FTP class has five public events as described in "The FTP Class's Events" section.
The FTP class contains the following variable declaration:
Private port As Integer = 21 Private controlSocket, dataSocket As Socket Private serverAddress As String Private directoryListField As String 'the thread used for uploading and downloading files Private dataTransferThread As Thread 'indicates whether dataTransferThread is being used 'if it is, do not allow another operation Private transferring As Boolean 'for transferring filename and localDir when calling DoUpload and 'DoDownload Private filename, localDir As String Public replyMessage As String Public replyCode As String
The FTP class has two read-only properties.
The Connected property indicates whether the control socket is connected:
Public ReadOnly Property Connected() As Boolean Get If Not controlSocket Is Nothing Then Return controlSocket.Connected Else Return False End If End Get End Property
The DirectoryList property returns the directory list obtained from the GetDirList method in raw form:
Public ReadOnly Property DirectoryList() As String Get Return directoryListField End Get End Property
The following sections describe the methods in the FTP class.
The ChangeDir method changes the current remote directory by sending the CWD command:
Public Sub ChangeDir(ByVal path As String) SendCommand("CWD " & path & ControlChars.CrLf) GetResponse() End Sub
The ChangeToAsciiMode method changes the transfer mode to ASCII:
Public Sub ChangeToAsciiMode() SendTYPECommand("A") End Sub
This method connects to a remote server:
Public Sub Connect(ByVal server As String) Try controlSocket = New _ Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) controlSocket.Connect(New _ IPEndPoint(Dns.Resolve(server).AddressList(0), port)) Catch e As Exception Console.WriteLine(e.ToString()) Return End Try If controlSocket.Connected Then Console.WriteLine("Connected. Waiting for reply...") GetResponse() Else Console.WriteLine("Couldn't connect.") End If End Sub
The DeleteDir method deletes a directory on the connected server by sending an RMD command. A 2xx reply code indicates a successful RMD command. The method definition is as follows:
Public Function DeleteDir(ByVal dir As String) As Boolean SendCommand("RMD " & dir & ControlChars.CrLf) GetResponse() If replyCode.StartsWith("2") Then Return True Else Return False End If End Function
The DeleteFile method deletes a file on the connected server by sending a DELE command. A 2xx reply code indicates a successful DELE command. The method definition is as follows:
Public Function DeleteFile(ByVal filename As String) As Boolean SendCommand("DELE " & filename & ControlChars.CrLf) GetResponse() If replyCode.StartsWith("2") Then Return True Else Return False End If End Function
The Disconnect method disconnects from the remote server:
Public Sub Disconnect() If controlSocket.Connected Then SendCommand("QUIT" & ControlChars.CrLf) GetResponse() controlSocket.Shutdown(SocketShutdown.Both) controlSocket.Close() End If End Sub
The DoDownload method does the actual file download. This method is called from the Download method. The following is the DoDownload method:
Public Sub DoDownload() OnBeginDownload(New EventArgs()) Dim completePath As String = Path.Combine(localDir, filename) Try Dim f As FileStream = File.Create(completePath) SendTYPECommand("I") PassiveDataConnection() SendCommand("RETR " & filename & ControlChars.CrLf) GetResponse() Dim byteReceivedCount As Integer Dim totalByteReceived As Integer = 0 Dim bytes(511) As Byte Do byteReceivedCount = _ dataSocket.Receive(bytes, bytes.Length, SocketFlags.None) totalByteReceived += byteReceivedCount f.Write(bytes, 0, byteReceivedCount) OnTransferProgressChanged(New _ TransferProgressChangedEventArgs(totalByteReceived)) Loop Until byteReceivedCount = 0 f.Close() 'because the 226 response might be sent 'before the data connection finishes, only try to get "completion message" 'if it's not yet sent If replyMessage.IndexOf("226 ") = -1 Then GetResponse() End If SendTYPECommand("A") Catch End Try Dim e As New EndDownloadEventArgs() e.Message = "Finished downloading " & filename OnEndDownload(e) transferring = False End Sub
The DoDownload method starts by raising the BeginDownload event:
OnBeginDownload(New EventArgs())
The DoDownload method is the method assigned to a new thread created in the Download method. Prior to starting the new thread, the Download method sets the localDir and filename variables. The localDir is the current local directory to which the downloaded file will be saved. The filename variable contains the name of the file to be downloaded.
The next thing the DoDownload method does after raising the BeginDownload event is combine localDir and filename:
Dim completePath As String = Path.Combine(localDir, filename)
Next, it creates a file on the local machine using the combined string of localDir and filename:
Dim f As FileStream = File.Create(completePath)
Then, it changes the transfer mode to image (binary) and calls the PassiveDataConnection method. The latter constructs a data socket to be used for the file transfer:
SendTYPECommand("I") PassiveDataConnection()
The actual file download starts when a RETR command is sent:
SendCommand("RETR " & filename & ControlChars.CrLf) GetResponse()
Then, the data socket resulted from the PassiveDataConnection method receives the data stream:
Dim byteReceivedCount As Integer Dim totalByteReceived As Integer = 0 Dim bytes(511) As Byte Do byteReceivedCount = _ dataSocket.Receive(bytes, bytes.Length, SocketFlags.None) totalByteReceived += byteReceivedCount f.Write(bytes, 0, byteReceivedCount) OnTransferProgressChanged(New _ TransferProgressChangedEventArgs(totalByteReceived)) Loop Until byteReceivedCount = 0
Note from the previous Do loop that the TransferProgressChanged event raises after each invocation of the data socket's Received method, passing the total number of bytes received so far. The user of the FTP class can use this event to notify the user of the progress of the file transfer, for example, by using a progress bar.
Afterward, the file closes:
f.Close()
After the file transfer completes, the server sends the 226 reply code. However, sometimes this reply code is received even before the whole data transferred is received. Therefore, you check to see that a 226 reply code has not been received prior to calling the GetResponse method:
If replyMessage.IndexOf("226 ") = -1 Then GetResponse() End If
It then changes the mode to ASCII:
SendTYPECommand("A") Catch End Try
Finally, the EndDownload event triggers, passing the "Finished downloading filename" message, where filename is the name of the file downloaded and the transferring Boolean resets to allow a future file transfer:
Dim e As New EndDownloadEventArgs() e.Message = "Finished downloading " & filename OnEndDownload(e) transferring = False
The DoUpload method does the actual file upload. This method is called from the Upload method. The DoUpload method starts by raising the BeginUpload event:
OnBeginUpload(New EventArgs())
The DoUpload method is the method assigned to a new thread created in the Upload method. Prior to starting the new thread, the Upload method sets the localDir and filename variables. The localDir is the current local directory to which the downloaded file will be saved. The filename variable contains the name of the file to be downloaded.
The next thing the DoUpload method does after raising the BeginUpload event is combine localDir and filename:
Dim completePath As String = Path.Combine(localDir, filename)
Then it opens the file to upload, changes the mode to ASCII, and calls the PassiveDataConnection method. The PassiveDataConnection method constructs a data socket to be used for the file transfer:
Dim f As FileStream = _ File.Open(completePath, FileMode.Open, FileAccess.Read) SendTYPECommand("I") PassiveDataConnection()
The actual file upload starts when a STOR command is sent:
SendCommand("STOR " & filename & ControlChars.CrLf) GetResponse()
Then, the data socket resulted from the PassiveDataConnection method receives the data stream:
Dim byteReadCount As Integer Dim totalByteSent As Integer Dim bytes(511) As Byte Do byteReadCount = f.Read(bytes, 0, bytes.Length) If byteReadCount <> 0 Then dataSocket.Send(bytes, byteReadCount, SocketFlags.None) totalByteSent += byteReadCount OnTransferProgressChanged( _ New TransferProgressChangedEventArgs(totalByteSent)) End If Loop Until byteReadCount = 0
Note from the previous Do loop that the TransferProgressChanged event raises after each invocation of the data socket's Send method, passing the total number of bytes sent so far. The user of the FTP class can use this event to notify the user of the progress of the file transfer, for example, by using a progress bar.
Afterward, the data socket and the file close:
dataSocket.Shutdown(SocketShutdown.Both) dataSocket.Close() f.Close()
When the data socket closes, the server knows that the file transfer is completed and sends a reply code that you receive using the GetResponse method:
GetResponse()
Then, it changes the mode back to ASCII:
SendTYPECommand("A")
Finally, the EndUpload event triggers, passing the "Finished uploading filename" message, where filename is the name of the file uploaded and the transferring Boolean resets to allow a future file transfer:
Dim ev As New EndUploadEventArgs() ev.Message = "Finished uploading " & filename OnEndUpload(ev) transferring = False
The Download method checks if file transfer is allowed and, if it is, creates a new thread to download a file:
Public Sub Download(ByVal filename As String, ByVal localdir As String) If Not transferring Then transferring = True Me.filename = filename Me.localDir = localdir dataTransferThread = _ New Thread(New ThreadStart(AddressOf DoDownload)) dataTransferThread.Start() End If End Sub
GetRemoteDirectory is a helper method used to obtain the directory name at the remote server. The argument passed to this function is a string that is the server reply after a PWD command is sent. Therefore, the argument has the following format:
"path" is current directory
This method returns the "path" portion of the argument. The definition for this method is as follows:
Private Function GetRemoteDirectory(ByVal message As String) As String 'message is the server response upon sending the "PWD" command 'its format is something like: "path" is current directory 'this function obtains the string between the double quotes Dim path As String = "" Dim index As Integer = message.IndexOf("""") If index <> -1 Then Dim index2 As Integer = message.IndexOf("""", index + 1) If index2 <> -1 Then path = message.Substring(index + 1, index2 - index - 1) End If End If Return path End Function
The GetResponse method receives the server reply and is the same as the GetResponse method in the NETFTP class.
The Login method logs in to a connected FTP server and is similar to the Login method in the NETFTP class. The difference is that this method returns a Boolean because the server's reply is tested at the end of the method, as follows:
If replyCode.Equals("230") Then Return True Else Return False End If
The MakeDir method creates a new directory in the remote server by sending an MKD command:
Public Sub MakeDir(ByVal dir As String) SendCommand("MKD " & dir & ControlChars.CrLf) GetResponse() End Sub
The PassiveDataConnection method is the same as the PassiveDataConnection method in the NETFTP class.
The Rename method changes the name of a remote file. It does so by sending the RNFR command and the RNTO command in sequence. Its definition is as follows:
Public Sub Rename(ByVal renameFrom As String, ByVal renameTo As String) SendCommand("RNFR " & renameFrom & ControlChars.CrLf) GetResponse() Console.WriteLine(replyCode & " " & replyMessage) SendCommand("RNTO " & renameTo & ControlChars.CrLf) GetResponse() Console.WriteLine(replyCode & " " & replyMessage) End Sub
The SendCommand method sends a specified command to the connected server. Its definition is as follows:
Private Sub SendCommand(ByVal command As String) Try controlSocket.Send(Encoding.ASCII.GetBytes(command), command.Length, 0) Catch End Try End Sub
The SendTYPECommand method is the same as the SendTYPECommand method in the NETFTP class.
The Upload method checks if file transfer is allowed by checking the value of transferring. If the value is False, file transfer is allowed. It then creates a new thread for the file transfer and starts the DoUpload method:
Public Sub Upload(ByVal filename As String, ByVal localDir As String) If Not transferring Then transferring = True Me.filename = filename Me.localDir = localDir dataTransferThread = New Thread(New ThreadStart(AddressOf DoUpload)) dataTransferThread.Start() End If End Sub
The FTP class can raise the following events: BeginDownload, EndDownload, BeginUpload, EndUpload, and TransferProgressChanged. The first four are self- explanatory. The TransferProgressChanged event raises several times during the download and upload processes. The user of the FTP class can capture this event to obtain the number of bytes of data transfer so far.
The definitions of public delegates are as follows:
Public Delegate Sub BeginDownloadEventHandler(ByVal sender As Object, _ ByVal e As EventArgs) Public Delegate Sub EndDownloadEventHandler(ByVal sender As Object, _ ByVal e As EndDownloadEventArgs) Public Delegate Sub BeginUploadEventHandler(ByVal sender As Object, _ ByVal e As EventArgs) Public Delegate Sub EndUploadEventHandler(ByVal sender As Object, _ ByVal e As EndUploadEventArgs) Public Delegate Sub TransferProgressChangedEventHandler(ByVal sender As Object, _ ByVal e As TransferProgressChangedEventArgs)
The following are the three subclasses of EventArgs class:
Public Class EndDownloadEventArgs : Inherits EventArgs Public Message As String End Class Public Class EndUploadEventArgs : Inherits EventArgs Public Message As String End Class Public Class TransferProgressChangedEventArgs : Inherits EventArgs Public TransferredByteCount As Integer Public Sub New() End Sub Public Sub New(ByVal size As Integer) TransferredByteCount = size End Sub End Class
You can find the Form1 class in the Form1.vb file in the project's directory. It represents the main form in the application. This section starts by showing various controls used in the form. It then describes how those controls connect together. The description makes frequent references to the class's members, each of which is given in detail at the end of the section.
To understand how the form works, let's start with its visual description. Figure 5-5 shows various controls on Form1.
Figure 5-5: Control names on Form1
You can find the declaration of the controls in the Form1 class body:
Private hSplitter As System.Windows.Forms.Splitter Private vSplitter As System.Windows.Forms.Splitter Private leftPanel As System.Windows.Forms.Panel Private rightPanel As System.Windows.Forms.Panel Private localButtonsPanel As System.Windows.Forms.Panel Private remoteButtonsPanel As System.Windows.Forms.Panel Private progressBar As System.Windows.Forms.ProgressBar Private label1 As System.Windows.Forms.Label Private label2 As System.Windows.Forms.Label Private localDir As System.Windows.Forms.ComboBox Private localDeleteButton As System.Windows.Forms.Button Private localRenameButton As System.Windows.Forms.Button Private localMakeDirButton As System.Windows.Forms.Button Private localDirList As System.Windows.Forms.ListView Private uploadButton As System.Windows.Forms.Button Private remoteDir As System.Windows.Forms.ComboBox Private remoteDirList As System.Windows.Forms.ListView Private remoteDeleteButton As System.Windows.Forms.Button Private downloadButton As System.Windows.Forms.Button Private remoteRenameButton As System.Windows.Forms.Button Private remoteMakeDirButton As System.Windows.Forms.Button Private messageTextBox As System.Windows.Forms.TextBox Private mainMenu As System.Windows.Forms.MainMenu Private fileMenuItem As System.Windows.Forms.MenuItem Private connectFileMenuItem As System.Windows.Forms.MenuItem Private exitFileMenuItem As System.Windows.Forms.MenuItem Private imageList As System.Windows.Forms.ImageList
When first instantiated, the class's constructor calls the InitializeComponent method that instantiated the controls used in the form. At the last line, the InitializeComponent method calls the InitializeControls method, which wires events with event handlers, loads images, and so on.
The form has an ImageList control with three images used to represent a parent directory, a folder, and a file. The image files (Up.gif, Folder.gif, and File.gif) are located in the images directory under the project's directory. You add the images to imageList in the InitializeControls method:
imageList.Images.Add(Bitmap.FromFile("./images/Up.gif")) imageList.Images.Add(Bitmap.FromFile("./images/Folder.gif")) imageList.Images.Add(Bitmap.FromFile("./images/File.gif"))
At the end of its body, the InitializeControls method calls the following two methods:
SelectLocalDirectory(localCurrentDir) Log("Welcome. Press F3 for quick login.")
The SelectLocalDirectory method populates the localDirList control with the list of subdirectories/files in the current directory, and the Log method displays a message in the messageTextBox control. The current local directory displays in the localDir ComboBox control.
Without being connected to a remote server, you can browse through your local directory by double-clicking the parent directory icon and any subdirectory in the current directory.
You can even do some simple file/directory manipulations such as the following:
Create a new directory by clicking the localMakeDirButton control. The Click event of the localMakeDirButton control is handled by localMakeDirButton_Click, which calls the MakeLocalDir method.
Delete a file/subdirectory by selecting a file/directory in the localDirList control and clicking the localDeleteButton control. The Click event of the localDirList control is wired with the localDeleteButton_Click event handler. This event handler calls the DeleteLocalFile method.
Rename a file/directory by selecting a file/directory in the localDirList control and clicking the localRenameButton control. This button's Click event is wired to the localRenameButton_Click event handler, which calls the RenameLocalFile method.
However, using an FTP client application, you will want to connect to a remote server. You do this by pressing F3 or by selecting File Connect. Both the F3 shortcut and the Connect menu item activates the Connect method. The Connect method has two functions. When no FTP server is connected, it connects to the server. When there is an FTP server connected, it disconnects the connection.
Connecting to a remote server requires you to enter the server name, username, and password into the Login Form window. The Login Form window is called from the Connect method and is shown as a modal dialog box.
If you click the OK button in the Login Form window, the Connect method tries to connect you to the remote server. If the connection is successful, it also logs you in. Whether login was successful, it calls the Log method. This method appends the specified message to the messageTextBox control.
If login is successful, the Connect method displays the remote server's home directory content on the remoteDirList control.
The Form1 class has the following declarations part:
Private localCurrentDir As String = Directory.GetCurrentDirectory() Private remoteCurrentDir As String Private server, userName, password As String Private ftp As New ftp() 'the size of the file being downloaded/uploaded Private fileSize As Integer Private Structure DirectoryItem Public name As String Public modifiedDate As String Public size As String End Structure
Note that the DirectoryItem structure represents either a directory or a file.
The following sections describe the Form1 class's methods.
The ChangeLocalDir method changes the local directory. This method is called when the user double-clicks an item in the localDirList control. The action taken by this method depends on the item activated. It changes directory if the activated item is either the parent directory icon or a folder. If the item is a file, the ChangeLocalDir method calls the UploadFile method.
This is the ChangeLocalDir method:
Private Sub ChangeLocalDir() 'get activated item (the items that was double-clicked Dim item As ListViewItem = localDirList.SelectedItems(0) If item.Text.Equals("..") Then Dim parentDir As DirectoryInfo = Directory.GetParent(localCurrentDir) If Not parentDir Is Nothing Then localCurrentDir = parentDir.FullName SelectLocalDirectory(localCurrentDir) End If Else Directory.SetCurrentDirectory(localCurrentDir) Dim fullPath As String = Path.GetFullPath(item.Text) If Helper.IsDirectory(fullPath) Then localCurrentDir = fullPath SelectLocalDirectory(localCurrentDir) Else UploadFile() End If End If End Sub
This method begins by obtaining the selected item from localDirList:
'get activated item (the items that was double-clicked Dim item As ListViewItem = localDirList.SelectedItems(0)
It then checks whether the item is a parent directory icon. If it is, it constructs a DirectoryInfo object for the parent directory of the current directory using the GetParent method of the System.IO.Directory class:
If item.Text.Equals("..") Then Dim parentDir As DirectoryInfo = Directory.GetParent(localCurrentDir)
If the GetParent method returns a non-null value, it sets localCurrentDir to the parent directory's full name and calls the SelectLocalDirectory method to repopulate the localDirList control:
If Not parentDir Is Nothing Then localCurrentDir = parentDir.FullName SelectLocalDirectory(localCurrentDir) End If
If the activated item is not a parent directory icon, it sets the application current directory to the value of localCurrentDir so that it can get the full path to the currently activated item:
Else Directory.SetCurrentDirectory(localCurrentDir) Dim fullPath As String = Path.GetFullPath(item.Text)
Now, it has to determine whether the activated item is a file or a directory using the Helper class's IsDirectory method. If it is a directory, it sets localCurrentDir to the full path obtained from the GetFullPath method in the previous line and then calls the SelectLocalDirectory to repopulate the localDirList control:
If Helper.IsDirectory(fullPath) Then localCurrentDir = fullPath SelectLocalDirectory(localCurrentDir)
If the activated item is a file, the ChangeLocalDir method simply calls the UploadFile method:
UploadFile()
The ChangeRemoteDir method changes the directory in the connected server:
Private Sub ChangeRemoteDir() If ftp.Connected Then 'get activated item (the item that was double-clicked) Dim item As ListViewItem = remoteDirList.SelectedItems(0) If item.Text.Equals("..") Then Dim index As Integer 'get the last index of "/" index = remoteCurrentDir.LastIndexOf("/") If index = 0 Then remoteCurrentDir = "/" Else remoteCurrentDir = remoteCurrentDir.Substring(0, index) End If ftp.ChangeDir(remoteCurrentDir) If ftp.replyCode.StartsWith("2") Then 'successful SelectRemoteDirectory(remoteCurrentDir) End If Log(ftp.replyMessage) ElseIf Helper.IsDirectoryItem(remoteDirList.SelectedItems(0)) Then If remoteCurrentDir.Equals("/") Then remoteCurrentDir += item.Text Else remoteCurrentDir += "/" & item.Text End If ftp.ChangeDir(remoteCurrentDir) If ftp.replyCode.StartsWith("2") Then 'successful SelectRemoteDirectory(remoteCurrentDir) End If Log(ftp.replyMessage) Else DownloadFile() End If Else NotConnected() End If End Sub
First, the method only executes the code in its body if a remote server is connected. Checking a connection is through the FTP class's Connected property:
If ftp.Connected Then ... Else NotConnected() End If
After making sure that a remote server is connected, it obtains the activated item from the remoteDirList control:
'get activated item (the item that was double-clicked) Dim item As ListViewItem = remoteDirList.SelectedItems(0)
The action taken by this method depends on the activated item. If the item is a parent directory icon or a folder, it calls the FTP class's ChangeDir method. If the activated item is a file, it calls the FTP class's Download method.
Note that because you are dealing with a remote server, you do not have access to the directory system. Instead, you work with paths.
If the activated item is the parent directory icon, the method tries to obtain the parent directory of the remote current directory. The remoteCurrentDir variable holds the remote current directory. Obtaining the parent directory is by trimming the characters after the last / (assuming that the remote server uses a Unix directory listing):
If item.Text.Equals("..") Then Dim index As Integer 'get the last index of "/" index = remoteCurrentDir.LastIndexOf("/") If index = 0 Then ' we are already in the root remoteCurrentDir = "/" Else remoteCurrentDir = remoteCurrentDir.Substring(0, index) End If
This gives you a new remote current directory. You just need to call the FTP class's ChangeDir method and pass the new directory name:
ftp.ChangeDir(remoteCurrentDir)
Now, if the ChangeDir method returns a successful reply message, you call the SelectRemoteDirectory to repopulate the remoteDirList control. You also log the reply message from the server:
If ftp.replyCode.StartsWith("2") Then 'successful SelectRemoteDirectory(remoteCurrentDir) End If Log(ftp.replyMessage)
If the activated item is a folder, the user clicks a subdirectory in the current remote directory. You can obtain the new current directory by appending the item's text to the current directory. Note, however, if you are currently in the root, you do not append a / before appending the directory name:
ElseIf Helper.IsDirectoryItem(remoteDirList.SelectedItems(0)) Then If remoteCurrentDir.Equals("/") Then remoteCurrentDir += item.Text Else remoteCurrentDir += "/" & item.Text End If
Having a new directory, you call the FTP class's ChangeDir, passing the destination directory:
ftp.ChangeDir(remoteCurrentDir)
If the ChangeDir method returns a server's successful message, you call the SelectRemoteDirectory to repopulate the remoteDirList control. You also log the server's reply message:
If ftp.replyCode.StartsWith("2") Then 'successful SelectRemoteDirectory(remoteCurrentDir) End If Log(ftp.replyMessage)
If the activated item is a file, you call the DownloadFile method:
DownloadFile()
The Connect method connects to and disconnects from a remote server. If the connectFileMenuItem's Text displays Disconnect, the application may be connected to a remote server. Because the remote server can disconnect a client if the client is idle for a given period of time, it is possible that the application is not connected to any server even though the client thinks it is still connected. Either way, the Connect method handles the situation well. The definition of the Connect method is as follows:
Private Sub Connect() 'connect and disconnect If connectFileMenuItem.Text.Equals("&Disconnect") Then 'disconnect If MessageBox.Show("Disconnect from remote server?", "Disconnect", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Question) = _ DialogResult.OK Then If ftp.Connected Then ftp.Disconnect() Log("Disconnected.") connectFileMenuItem.Text = "&Connect" 'clearing the ListView 'don't use the remoteDirList.Clear because it removes the columns too, 'instead use remoteDirList.Items.Clear() remoteDirList.Items.Clear() 'clearing the combo box remoteDir.Items.Clear() remoteDir.Text = "" End If End If Else 'connect Dim loginForm As New LoginForm() Dim loggedIn As Boolean = False While Not loggedIn AndAlso loginForm.ShowDialog() = DialogResult.OK server = loginForm.Server userName = loginForm.UserName password = loginForm.Password Log("Connecting " & server) Try ftp.Connect(server) If ftp.Connected Then Log(server & " connected. Try to login.") If ftp.Login(userName, password) Then connectFileMenuItem.Text = "&Disconnect" Log("Login successful.") loggedIn = True ' try to get the remote list ftp.ChangeToAsciiMode() remoteCurrentDir = ftp.GetCurrentRemoteDir() If Not remoteCurrentDir Is Nothing Then SelectRemoteDirectory(remoteCurrentDir) End If Else Log("Login failed.") End If Else Log("Connection failed") End If Catch e As Exception Log(e.ToString()) End Try End While If Not loggedIn AndAlso _ Not ftp Is Nothing AndAlso _ ftp.Connected Then ftp.Disconnect() End If End If End Sub
The Connect method first checks the connectFileMenuItem's Text property. If it is &Disconnect, it tries to disconnect from the connected remote server:
If connectFileMenuItem.Text.Equals("&Disconnect") Then 'disconnect
Before disconnecting, it asks for the user's confirmation:
If MessageBox.Show("Disconnect from remote server?", "Disconnect", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Question) = _ DialogResult.OK Then
If the user says OK, it checks if the application is really connected to a remote server. If it is, it calls the FTP class's Disconnect method, logs a message, changes the connectFileMenuItem's Text to &Connect, and clears the remoteDirList and remoteDir controls:
If ftp.Connected Then ftp.Disconnect() Log("Disconnected.") connectFileMenuItem.Text = "&Connect" 'clearing the ListView 'don't use the remoteDirList.Clear because it removes the columns too, 'instead use remoteDirList.Items.Clear() remoteDirList.Items.Clear() 'clearing the combo box remoteDir.Items.Clear() remoteDir.Text = "" End If
If no server is connected, the Connect method tries to connect. It starts by defining a Boolean called loggedIn and showing the Login Form window as a modal dialog box. It then does a While loop that loops until one of the following conditions is satisfied:
The user clicks the Cancel button on the Login Form window.
The user clicks the OK button on the Login Form window and logs in successfully. This is the code that does that:
'connect Dim loginForm As New LoginForm() Dim loggedIn As Boolean = False While Not loggedIn AndAlso loginForm.ShowDialog() = DialogResult.OK
When the user clicks the OK button, it takes the values of the Login Form window's Server, UserName, and Password properties and logs a message:
server = loginForm.Server userName = loginForm.UserName password = loginForm.Password Log("Connecting " & server)
It then tries to connect to the specified server using the FTP class's Connect method:
Try ftp.Connect(server)
If the connection is successful, it logs a message and tries to log in using the FTP class's Login method, passing the username and password:
If ftp.Connected Then Log(server & " connected. Try to login.") If ftp.Login(userName, password) Then
The Login method returns True if the user logs in successfully and False otherwise. For a successful login, you change the connectFileMenuItem's Text property to Disconnect, log a successful login message, and change the transfer mode by calling the ChangeToAsciiMode of the FTP class:
connectFileMenuItem.Text = "&Disconnect" Log("Login successful.") loggedIn = True ' try to get the remote list ftp.ChangeToAsciiMode()
Next, it tries to obtain the remote current directory by calling the GetCurrentRemoteDir method of the FTP class:
remoteCurrentDir = ftp.GetCurrentRemoteDir()
If this method executes successfully on the remote server, it should return a non-null value, the remote current directory. You then use it as an argument to the SelectRemoteDirectory that displays the content of the remote current directory:
If Not remoteCurrentDir Is Nothing Then SelectRemoteDirectory(remoteCurrentDir) End If
If the FTP class's Login method you called returns False, you log a "Login failed" message:
Log("Login failed.")
The DeleteLocalFile method deletes a local file and is called when the user selects an item in the localDirList control and clicks the localDeleteButton control. The definition of this method is as follows:
Private Sub DeleteLocalFile() Dim selectedItemCount As Integer = localDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to delete.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) Else If MessageBox.Show("Delete the selected file/directory?", _ "Delete Confirmation", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Question) _ = DialogResult.OK Then Dim completePath As String = _ Path.Combine(localCurrentDir, localDirList.SelectedItems(0).Text) Try If Helper.IsDirectory(completePath) Then Directory.Delete(completePath) Else File.Delete(completePath) End If LoadLocalDirList() Catch ex As Exception MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, _ MessageBoxIcon.Error) End Try End If End If End Sub
The DeleteLocalFile starts by checking if an item is selected in the localDirList control. If not, it displays a message box:
Dim selectedItemCount As Integer = localDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to delete.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
If an item is selected, the DeleteLocalFile method asks for the user confirmation that the user intends to delete the item to make sure that the user did not click the localDeleteButton control by accident:
If MessageBox.Show("Delete the selected file/directory?", _ "Delete Confirmation", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Question) _ = DialogResult.OK Then
If deletion is confirmed, it tries to obtain the complete path to the item by combining the local current directory and the item's text:
Dim completePath As String = _ Path.Combine(localCurrentDir, localDirList.SelectedItems(0).Text)
Then, it checks whether it is a directory or a file. If it is a directory, it calls the Delete method of the System.IO.Directory class. If it is a file, the Delete method of the System.IO.File class is invoked:
If Helper.IsDirectory(completePath) Then Directory.Delete(completePath) Else File.Delete(completePath) End If
After deletion, the localDirList is refreshed by calling the LoadLocalDirList method:
LoadLocalDirList()
The DeleteRemoteFile method deletes a file on the connected remote server:
Private Sub DeleteRemoteFile() If ftp.Connected Then Dim selectedItemCount As Integer = remoteDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to delete.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) Else If MessageBox.Show("Delete the selected file/directory?", _ "Delete Confirmation", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Question) _ = DialogResult.OK Then Try Dim selectedItem As ListViewItem = remoteDirList.SelectedItems(0) If Helper.IsDirectoryItem(selectedItem) Then If ftp.DeleteDir(selectedItem.Text) Then LoadRemoteDirList() Else Log(ftp.replyMessage) End If Else If ftp.DeleteFile(selectedItem.Text) Then LoadRemoteDirList() Else Log(ftp.replyMessage) End If End If Catch ex As Exception MessageBox.Show(ex.ToString, "Error", MessageBoxButtons.OK, _ MessageBoxIcon.Error) End Try End If End If Else NotConnected() End If End Sub
Note that the method only executes its body if the application is connected to a remote FTP server:
If ftp.Connected Then ... Else NotConnected() End If
After making sure that a remote server is connected, it checks that an item is selected in the remoteDirList control. If no item is selected, a warning displays in a message box:
Dim selectedItemCount As Integer = remoteDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to delete.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
If an item is selected, it asks for the user's confirmation to make sure that the remoteDeleteButton control was not clicked by accident:
Else If MessageBox.Show("Delete the selected file/directory?", _ "Delete Confirmation", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Question) _ = DialogResult.OK Then
If deletion is confirmed, the method obtains the selected item and sends it to the Helper class's IsDirectoryItem to determine if the selected item is a directory or a file:
Try Dim selectedItem As ListViewItem = remoteDirList.SelectedItems(0) If Helper.IsDirectoryItem(selectedItem) Then
If the selected item is a directory, it calls the FTP class's DeleteDir method and, upon successful completion of this method, calls the LoadRemoteDirList to repopulate the remoteDirList control. If the DeleteDir method returns False to indicate that the deletion failed, it logs the message:
If ftp.DeleteDir(selectedItem.Text) Then LoadRemoteDirList() Else Log(ftp.replyMessage) End If
If the selected item is a file, it calls the FTP class's DeleteFile method and, upon successful completion of this method, calls the LoadRemoteDirList to repopulate the remoteDirList control. If the DeleteFile method failed, it logs the message:
If ftp.DeleteFile(selectedItem.Text) Then LoadRemoteDirList() Else Log(ftp.replyMessage) End If
The DownloadFile method downloads a file from the connected remote server:
Private Sub DownloadFile() If ftp.Connected Then Dim selectedItemCount As Integer = remoteDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file to download.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) Else Dim item As ListViewItem = remoteDirList.SelectedItems(0) If Helper.IsDirectoryItem(item) Then MessageBox.Show("You cannot download a directory.", _ "Error downloading file", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else Try fileSize = Convert.ToInt32(item.SubItems(1).Text) Catch End Try ftp.Download(item.Text, localCurrentDir) End If End If Else NotConnected() End If End Sub
Note that the method only executes its body if the application is connected to a remote FTP server:
If ftp.Connected Then ... Else NotConnected() End If
After making sure that a remote server is connected, it checks that an item is selected in the remoteDirList control. If no item is selected, a warning displays:
Dim selectedItemCount As Integer = remoteDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file to download.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
If an item is selected, it gets the selected item from the remoteDirList control and sends the item to the Helper class's IsDirectoryItem method to determine whether the selected item is a directory or a file:
Dim item As ListViewItem = remoteDirList.SelectedItems(0)
If the item is a directory, the method shows an error message, warning the user that they cannot download a directory:
If Helper.IsDirectoryItem(item) Then MessageBox.Show("You cannot download a directory.", _ "Error downloading file", MessageBoxButtons.OK, _ MessageBoxIcon.Error)
If the selected item is a file, it gets the file size from the item and assigns it to the fileSize variable. The FTP class's BeginDownload event handler uses this value to calculate the transfer progress:
Try fileSize = Convert.ToInt32(item.SubItems(1).Text) Catch End Try
It then calls the FTP class's Download method, passing the filename and the local current directory. These two arguments determine where to save the downloaded file:
ftp.Download(item.Text, localCurrentDir)
This FTP client application only works properly if the remote server returns a directory listing in Unix style. If this is the case, the list contains lines of data that include the directory/filename and each directory/file's meta information. The GetDirectoryItem method processes the line that contains a directory/file information and returns it as a DirectoryItem object. The resulting DirectoryItem object can contain information about a directory or a file.
The raw data passed to this method has the following format:
-rwxrwxrwx 1 owner group 11801 Jul 23 10:52 NETFTP.vb
or this format:
drwxrwxrwx 1 owner group 0 Jul 26 20:11 New Folder
The method definition is as follows:
Private Function GetDirectoryItem(ByVal s As String) As DirectoryItem 's is in the following format '-rwxrwxrwx 1 owner group 11801 Jul 23 10:52 NETFTP.vb ' 'or ' 'drwxrwxrwx 1 owner group 0 Jul 26 20:11 New Folder Dim dirItem As New DirectoryItem() If Not s Is Nothing Then Dim index As Integer index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing "drwxrwxrwx" part 'now s is in the following format '1 owner group 11801 Jul 23 10:52 NETFTP.vb ' 'or ' '1 owner group 0 Jul 26 20:11 New Folder index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing the '1' part 'now s is in the following format 'owner group 11801 Jul 23 10:52 NETFTP.vb ' 'or ' 'owner group 0 Jul 26 20:11 New Folder index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing the 'owner' part 'now s is in the following format 'group 11801 Jul 23 10:52 NETFTP.vb ' 'or ' 'group 0 Jul 26 20:11 New Folder index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing the 'group' part 'now s is in the following format '11801 Jul 23 10:52 NETFTP.vb ' 'or ' '0 Jul 26 20:11 New Folder 'now get the size. index = s.IndexOf(" "c) If index > 0 Then dirItem.size = s.Substring(0, index) s = s.Substring(index).TrimStart() 'removing the size 'now s is in the following format 'Jul 23 10:52 NETFTP.vb ' 'or ' 'Jul 26 20:11 New Folder 'now, get the 3 elements of the date part Dim date1, date2, date3 As String index = s.IndexOf(" "c) If index <> -1 Then date1 = s.Substring(0, index) s = s.Substring(index).TrimStart() index = s.IndexOf(" "c) If index <> -1 Then date2 = s.Substring(0, index) s = s.Substring(index).TrimStart() index = s.IndexOf(" "c) If index <> -1 Then date3 = s.Substring(0, index) dirItem.modifiedDate = date1 & " " & date2 & " " & date3 ' get the name dirItem.name = s.Substring(index).Trim() End If End If End If End If End If End If End If End If End If Return dirItem End Function
The method starts by constructing a DirectoryItem object. This object will be populated and returned to the function's caller:
Dim dirItem As New DirectoryItem()
First the method checks that s (the argument passed to this method) is not null:
If Not s Is Nothing Then
If s is not null, then the method finds the first space in s, modifies s so that s does not include the string before the space, and left-trims s until the next nonspace character:
Dim index As Integer index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing "drwxrwxrwx" part
s now has the following format:
1 owner group 11801 Jul 23 10:52 NETFTP.vb
or this format:
1 owner group 0 Jul 26 20:11 New Folder
Then, you do the same thing as you did just now:
index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing the '1' part
to get s in the following format:
owner group 11801 Jul 23 10:52 NETFTP.vb
or this format:
owner group 0 Jul 26 20:11 New Folder
And again:
index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing the 'owner' part
to get s in the following format:
group 11801 Jul 23 10:52 NETFTP.vb
or this format:
group 0 Jul 26 20:11 New Folder
And yet another one:
index = s.IndexOf(" "c) If index <> -1 Then s = s.Substring(index).TrimStart() 'removing the 'group' part
Now, s has the following format:
11801 Jul 23 10:52 NETFTP.vb
or this format:
0 Jul 26 20:11 New Folder
Now, you can get the size and do the same operation:
index = s.IndexOf(" "c) If index > 0 Then dirItem.size = s.Substring(0, index) s = s.Substring(index).TrimStart() 'removing the size
Afterward, s has the following format:
Jul 23 10:52 NETFTP.vb
or this one:
Jul 26 20:11 New Folder
Now, get the three elements of the date part:
Dim date1, date2, date3 As String index = s.IndexOf(" "c) If index <> -1 Then date1 = s.Substring(0, index) s = s.Substring(index).TrimStart() index = s.IndexOf(" "c) If index <> -1 Then date2 = s.Substring(0, index) s = s.Substring(index).TrimStart() index = s.IndexOf(" "c) If index <> -1 Then date3 = s.Substring(0, index) dirItem.modifiedDate = date1 & " " & date2 & " " & date3 ' get the name dirItem.name = s.Substring(index).Trim()
Finally, return the DirectoryItem object:
Return dirItem
The InitializeProgressBar method initializes the progress bar prior to file transfer:
Private Sub InitializeProgressBar() progressBar.Value = 0 progressBar.Maximum = fileSize End Sub
The InitializeProgressBar method sets the Value property to 0 and the Maximum property to fileSize. fileSize contains the number of bytes to transfer:
progressBar.Value = 0 progressBar.Maximum = fileSize
The LoadLocalDirList method populates the localDirList control with the content of the current directory. If the current directory is not the root, an icon representing a parent directory is also added. The method definition is as follows:
Private Sub LoadLocalDirList() localDirList.Items.Clear() Dim item As ListViewItem ' if current directory is not root, add pointer to parent dir If Not Directory.GetParent(localCurrentDir) Is Nothing Then item = New ListViewItem("..", 1) item.ImageIndex = 0 localDirList.Items.Add(item) End If ' list of directories Dim directories As String() = Directory.GetDirectories(localCurrentDir) Dim length As Integer = directories.Length Dim dirName As String For Each dirName In directories item = New ListViewItem(Path.GetFileName(dirName), 1) item.SubItems.Add("") item.SubItems.Add(Directory.GetLastAccessTime(dirName).ToString()) item.ImageIndex = 1 localDirList.Items.Add(item) Next 'list of files Dim files As String() = Directory.GetFiles(localCurrentDir) length = files.Length Dim fileName As String For Each fileName In files item = New ListViewItem(Path.GetFileName(fileName), 1) Dim fi As New FileInfo(fileName) item.SubItems.Add(Convert.ToString(fi.Length)) item.SubItems.Add(File.GetLastWriteTime(fileName).ToString()) item.ImageIndex = 2 localDirList.Items.Add(item) Next End Sub
The method starts by clearing the localDirList control and defining a ListViewItem called item:
localDirList.Items.Clear() Dim item As ListViewItem
If the current directory is not root, it adds a parent directory icon:
If Not Directory.GetParent(localCurrentDir) Is Nothing Then item = New ListViewItem("..", 1) item.ImageIndex = 0 localDirList.Items.Add(item) End If
Next, it adds all subdirectories in the current directory. You obtain the list of directories from the GetDirectories method of the System.IO.Directory class:
Dim directories As String() = Directory.GetDirectories(localCurrentDir) Dim length As Integer = directories.Length Dim dirName As String For Each dirName In directories item = New ListViewItem(Path.GetFileName(dirName), 1) item.SubItems.Add("") item.SubItems.Add(Directory.GetLastAccessTime(dirName).ToString()) item.ImageIndex = 1 localDirList.Items.Add(item) Next
Finally, it adds all files in the current directory. You obtain the list of files from the GetFiles method of the Directory class:
Dim files As String() = Directory.GetFiles(localCurrentDir) length = files.Length Dim fileName As String For Each fileName In files item = New ListViewItem(Path.GetFileName(fileName), 1) Dim fi As New FileInfo(fileName) item.SubItems.Add(Convert.ToString(fi.Length)) item.SubItems.Add(File.GetLastWriteTime(fileName).ToString()) item.ImageIndex = 2 localDirList.Items.Add(item) Next
The LoadRemoteDirList method populates the remoteDirList control with the content of the remote current directory. If the remote current directory is not the root, an icon representing a parent directory is also added. The definition of the LoadRemoteDirList is as follows:
Private Sub LoadRemoteDirList() If ftp.Connected Then remoteDirList.Items.Clear() Dim item As ListViewItem If Not remoteCurrentDir.Equals("/") Then item = New ListViewItem("..", 1) item.ImageIndex = 0 remoteDirList.Items.Add(item) End If Try ftp.ChangeDir(remoteCurrentDir) ftp.GetDirList() Dim lines As String() = _ ftp.DirectoryList.Split(Convert.ToChar(ControlChars.Cr)) Dim line As String Dim fileList As New ArrayList() Dim dirList As New ArrayList() For Each line In lines If line.Trim().StartsWith("-") Then ' a file fileList.Add(line) ElseIf line.Trim().StartsWith("d") Then ' a directory dirList.Add(line) End If Next ' now load subdirectories to DirListView Dim enumerator As IEnumerator = dirList.GetEnumerator While enumerator.MoveNext Dim dirItem As DirectoryItem = _ GetDirectoryItem(CType(enumerator.Current, String)) If Not dirItem.name Is Nothing Then item = New ListViewItem(dirItem.name, 1) item.SubItems.Add("") item.SubItems.Add(dirItem.modifiedDate) remoteDirList.Items.Add(item) End If End While enumerator = fileList.GetEnumerator While enumerator.MoveNext Dim dirItem As DirectoryItem = _ GetDirectoryItem(CType(enumerator.Current, String)) If Not dirItem.name Is Nothing Then item = New ListViewItem(dirItem.name, 2) item.SubItems.Add(diritem.size) item.SubItems.Add(dirItem.modifiedDate) remoteDirList.Items.Add(item) End If End While Catch e As Exception Debug.WriteLine(e.ToString()) End Try Else NotConnected() End If End Sub
The method starts by checking if the application is connected to a remote server. It only executes the rest of the code in its body if the application is connected:
If ftp.Connected Then ... Else NotConnected() End If
If the application is connected, the remoteDirList control is cleared and a ListViewItem variable called item is defined:
remoteDirList.Items.Clear() Dim item As ListViewItem
If the remote current directory is not root, the method adds the icon representing the parent directory:
If Not remoteCurrentDir.Equals("/") Then item = New ListViewItem("..", 1) item.ImageIndex = 0 remoteDirList.Items.Add(item) End If
Next, it changes directory to the remote current directory and calls the FTP class's GetDirList to obtain the directory listing:
Try ftp.ChangeDir(remoteCurrentDir) ftp.GetDirList()
The directory listing returns in a long string containing file and directory information in the remote current directory. The string then splits into lines:
Dim lines As String() = _ ftp.DirectoryList.Split(Convert.ToChar(ControlChars.Cr))
The subdirectories and files returned are not grouped by type, but you want to display subdirectories in one group and files in another. Therefore, you construct an ArrayList called fileList that will hold the list of files and an ArrayList named dirList to hold the list of directories:
Dim line As String Dim fileList As New ArrayList() Dim dirList As New ArrayList()
Then, each line that starts with a hyphen in a file is added to fileList and lines starting with d are added to dirList:
For Each line In lines If line.Trim().StartsWith("-") Then ' a file fileList.Add(line) ElseIf line.Trim().StartsWith("d") Then ' a directory dirList.Add(line) End If Next
Now, you can add all subdirectories to the remoteDirList control. Note that you use the GetDirectoryItem to convert the raw text to a DirectoryItem object:
Dim enumerator As IEnumerator = dirList.GetEnumerator While enumerator.MoveNext Dim dirItem As DirectoryItem = _ GetDirectoryItem(CType(enumerator.Current, String)) If Not dirItem.name Is Nothing Then item = New ListViewItem(dirItem.name, 1) item.SubItems.Add("") item.SubItems.Add(dirItem.modifiedDate) remoteDirList.Items.Add(item) End If End While
Next, you add all files to the remoteDirList control, again using the GetDirectoryItem method to get DirectoryItem objects:
enumerator = fileList.GetEnumerator While enumerator.MoveNext Dim dirItem As DirectoryItem = _ GetDirectoryItem(CType(enumerator.Current, String)) If Not dirItem.name Is Nothing Then item = New ListViewItem(dirItem.name, 2) item.SubItems.Add(diritem.size) item.SubItems.Add(dirItem.modifiedDate) remoteDirList.Items.Add(item) End If End While
The Log method appends the text passed to it to the messageTextBox control's Text property and forces the messageTextBox control to scroll to the end of the text:
Private Sub Log(ByVal message As String) messageTextBox.Text += message & ControlChars.CrLf 'forces the TextBox to scroll messageTextBox.SelectionStart = messageTextBox.Text.Length messageTextBox.ScrollToCaret() End Sub
The MakeLocalDir method creates a new directory under the local current directory:
Private Sub MakeLocalDir() Dim dirName As String = InputBox( _ "Enter the name of the directory to create in the local computer", _ "Make New Directory").Trim() If Not dirName.Equals("") Then Dim fullPath As String = Path.Combine(localCurrentDir, dirName) If Directory.Exists(fullPath) Then MessageBox.Show("Directory already exists.", _ "Error creating directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else If File.Exists(fullPath) Then MessageBox.Show("Directory name is the same as the name of a file.", _ "Error creating directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else Try Directory.CreateDirectory(fullPath) LoadLocalDirList() Catch e As Exception MessageBox.Show(e.ToString, _ "Error creating directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error) End Try End If End If End If End Sub
It begins by prompting the user to enter a name for the new directory:
Dim dirName As String = InputBox( _ "Enter the name of the directory to create in the local computer", _ "Make New Directory").Trim()
If the user enters a valid name, it gets the full path by combining the local current directory and the entered name:
If Not dirName.Equals("") Then Dim fullPath As String = Path.Combine(localCurrentDir, dirName)
Next, it checks if the directory already exists:
If Directory.Exists(fullPath) Then MessageBox.Show("Directory already exists.", _ "Error creating directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error)
It also checks if the directory name resembles a file in the same directory:
If File.Exists(fullPath) Then MessageBox.Show("Directory name is the same as the name of a file.", _ "Error creating directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error)
If the name is unique in the directory, it uses the CreateDirectory method of the System.IO.Directory class to create a new directory:
Directory.CreateDirectory(fullPath)
Upon a successful create operation, it refreshes the content of the localDirList control by calling the LoadLocalDirList method:
LoadLocalDirList()
The MakeRemoteDir method creates a new directory in the remote current directory. If the application is connected to a remote server, it prompts the user for a directory name. The MakeRemoteDir method definition is as follows:
Private Sub MakeRemoteDir() If ftp.Connected Then Dim dirName As String = InputBox( _ "Enter the name of the directory to create in the remote server", _ "Make New Directory").Trim() If Not dirName.Equals("") Then ftp.MakeDir(dirName) Log(ftp.replyMessage) If ftp.replyCode.StartsWith("2") Then LoadRemoteDirList() 'Dim item As New ListViewItem(dirName, 1) 'If remoteCurrentDir.Equals("/") Then ' remoteDirList.Items.Insert(1, item) 'Else ' remoteDirList.Items.Insert(0, item) 'End If End If End If Else NotConnected() End If End Sub
It starts by prompting the user to enter a directory name into an InputBox:
If ftp.Connected Then Dim dirName As String = InputBox( _ "Enter the name of the directory to create in the remote server", _ "Make New Directory").Trim()
If the name is not blank, it calls the FTP class's MakeDir method and logs the message:
If Not dirName.Equals("") Then ftp.MakeDir(dirName) Log(ftp.replyMessage)
If the MakeDir method is successful (indicated by a reply code starting with 2), it calls the LoadRemoteDirList method:
If ftp.replyCode.StartsWith("2") Then LoadRemoteDirList() End If
The NotConnected method is called every time another method finds out that the application is not connected to a remote server. It logs a message and then changes the connectFileMenuItem's Text property to &Connect. The method definition is as follows:
Private Sub NotConnected() Log("Not connected") connectFileMenuItem.Text = "&Connect" 'clearing the ListView 'don't use the remoteDirList.Clear because it removes the columns too, 'instead use remoteDirList.Items.Clear() remoteDirList.Items.Clear() 'clearing the combo box remoteDir.Items.Clear() remoteDir.Text = "" End Sub
The first thing it does is to log the "Not connected" message and change the connectFileMenuItem's Text property:
Log("Not connected") connectFileMenuItem.Text = "&Connect"
It then clears the remoteDirList and remoteDir controls:
remoteDirList.Items.Clear() 'clearing the combo box remoteDir.Items.Clear() remoteDir.Text = ""
The RenameLocalFile method renames a file in the local computer. This method is invoked when the user clicks the localRenameButton control. The method definition is as follows:
Private Sub RenameLocalFile() Dim selectedItemCount As Integer = localDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to rename.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) Else Dim newName As String = InputBox("Enter the new name", "Rename").Trim() If Not newName.Equals("") Then Dim item As ListViewItem = localDirList.SelectedItems(0) If newName.Equals(item.Text) Then MessageBox.Show("Please enter a different name from the " & _ "file/directory you are trying to rename.", _ "Error renaming file/directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else Dim fullPath As String = Path.Combine(localCurrentDir, item.Text) If Helper.IsDirectory(fullPath) Then Directory.Move(fullPath, Path.Combine(localCurrentDir, newName)) Else Dim fi As New FileInfo(fullPath) fi.MoveTo(Path.Combine(localCurrentDir, newName)) End If LoadLocalDirList() End If End If End If End Sub
It first checks if an item is selected in the localDirList control. If not, it shows a warning:
Dim selectedItemCount As Integer = localDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to rename.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
If an item is selected in the localDirList control, it prompts for a new name:
Dim newName As String = InputBox("Enter the new name", "Rename").Trim()
If the new name does not consist only of spaces, it gets the selected item from the localDirList control and checks the old name. If the old name is the same as the new name, it displays an error message:
If Not newName.Equals("") Then Dim item As ListViewItem = localDirList.SelectedItems(0) If newName.Equals(item.Text) Then MessageBox.Show("Please enter a different name from the " & _ "file/directory you are trying to rename.", _ "Error renaming file/directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error)
If the new name does not conflict with the old one, it composes the full path using the static Combine method of the System.IO.Path class:
Dim fullPath As String = Path.Combine(localCurrentDir, item.Text)
Then it checks whether the selected item is a directory or a file. If it is a file, the method calls the Move method of the System.IO.Directory class to change the name:
If Helper.IsDirectory(fullPath) Then Directory.Move(fullPath, Path.Combine(localCurrentDir, newName))
If the selected item is a file, the method constructs a FileInfo object and calls its MoveTo method to change the filename:
Dim fi As New FileInfo(fullPath) fi.MoveTo(Path.Combine(localCurrentDir, newName))
Finally, it invokes the LoadLocalDirList to refresh the content of the localDirList control:
LoadLocalDirList()
The RenameRemoteFile method renames a file on the connected remote server. It only runs the code in its body if the application is connected to a remote server. The method definition is as follows:
Private Sub RenameRemoteFile() If ftp.Connected Then Dim selectedItemCount As Integer = remoteDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to rename.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) Else Dim dirName As String = InputBox( _ "Enter the new name", "Rename").Trim() If Not dirName.Equals("") Then Dim item As ListViewItem = remoteDirList.SelectedItems(0) If dirName.Equals(item.Text) Then MessageBox.Show("Please enter a different name from the " & _ "file/directory you are trying to rename.", _ "Error renaming file/directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else ftp.Rename(item.Text, dirName) If ftp.replyCode.StartsWith("2") Then item.Text = dirName End If Log(ftp.replyCode & " " & ftp.replyMessage) End If End If End If Else NotConnected() End If End Sub
It starts by checking if there is a selected item in the remoteDirList control. If there is not, an error message displays:
If ftp.Connected Then Dim selectedItemCount As Integer = remoteDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file/directory to rename.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
If there is a selected item, the method prompts the user for a new name:
Dim dirName As String = InputBox( _ "Enter the new name", "Rename").Trim()
If the new name does not consists of spaces only, it gets the selected item and gets the old name in the item's Text property. It then compares the new name with the old name:
If Not dirName.Equals("") Then Dim item As ListViewItem = remoteDirList.SelectedItems(0)
If the new name equals the old name, it displays an error message:
If dirName.Equals(item.Text) Then MessageBox.Show("Please enter a different name from the " & _ "file/directory you are trying to rename.", _ "Error renaming file/directory", MessageBoxButtons.OK, _ MessageBoxIcon.Error)
Otherwise, it calls the FTP class's Rename method, passing both the old name and the new name:
ftp.Rename(item.Text, dirName)
If the Rename method successfully executes (indicated by a server reply code starting with 2), the method updates the item's Text property in the remoteDirList control:
If ftp.replyCode.StartsWith("2") Then item.Text = dirName End If
Finally, it logs the reply message from the server:
Log(ftp.replyCode & " " & ftp.replyMessage)
The SelectLocalDirectory method inserts the local current directory into the localDir combo box:
Private Sub SelectLocalDirectory(ByVal path As String) ' add current dir to the list localDir.Items.Remove(path) localDir.Items.Insert(0, path) 'this will trigger the localDir ComboBox's SelectedIndexChanged event localDir.SelectedIndex = 0 End Sub
Prior to insertion, it removes the same item to avoid duplication:
' add current dir to the list localDir.Items.Remove(path) localDir.Items.Insert(0, path)
The method then sets the SelectedIndex property of the localDir combo box to 0 to make the new item selected. This also triggers the combo box's SelectedIndexChanged event:
localDir.SelectedIndex = 0
If the application is connected to a remote server, this method inserts a remote directory name at the first position in the remoteDir combo box. The method definition is as follows:
Private Sub SelectRemoteDirectory(ByVal path As String) If ftp.Connected Then ' add current dir to thel ist remoteDir.Items.Remove(path) remoteDir.Items.Insert(0, path) 'this will trigger the remoteDir ComboBox's SelectedIndexChanged event remoteDir.SelectedIndex = 0 Else NotConnected() End If End Sub
The first thing the method does is to remove the same item to avoid duplication:
If ftp.Connected Then ' add current dir to thel ist remoteDir.Items.Remove(path) remoteDir.Items.Insert(0, path)
It then sets the SelectedIndex property to 0. This triggers the SelectedIndexChanged event of the remoteDir combo box:
remoteDir.SelectedIndex = 0
The UpdateLocalDir method is invoked when the localDir control's SelectedIndexChanged event is triggered. This event is raised when the user manually selects a directory from the localDir control or the index is changed programmatically. Then, this method refreshes the content of the localDirList control. The method definition is as follows:
Private Sub UpdateLocalDir() Dim selectedIndex As Integer = localDir.SelectedIndex If localDir.SelectedIndex <> -1 Then localCurrentDir = CType(localDir.Items(selectedIndex), String) LoadLocalDirList() End If End Sub
The UpdateProgressBar method is called repeatedly by the event handler that handles the TransferProgressChanged event. This method updates the value of the progress bar. The method definition is as follows:
Private Sub UpdateProgressBar(ByVal count As Integer) progressBar.Value = count End Sub
The UpdateRemoteDir method is invoked when the remoteDir control's SelectedIndexChanged event triggers. This event is raised when the user manually selects a directory from the remoteDir control or the index is changed programmatically. This is the UpdateRemoteDir method:
Private Sub UpdateRemoteDir() If ftp.Connected Then Dim selectedIndex As Integer = remoteDir.SelectedIndex If remoteDir.SelectedIndex <> -1 Then remoteCurrentDir = CType(remoteDir.Items(selectedIndex), String) LoadRemoteDirList() End If Else NotConnected() End If End Sub
When the application is connected to a remote server, this method refreshes the content of the remoteDirList control.
The UploadFile method uploads a file to the connected remote server if the application is connected to a remote server. This is the method definition:
Private Sub UploadFile() If ftp.Connected Then Dim selectedItemCount As Integer = localDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file to upload.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning) Else Dim item As ListViewItem = localDirList.SelectedItems(0) If Helper.IsDirectoryItem(item) Then MessageBox.Show("You cannot upload a directory.", _ "Error uploading file", MessageBoxButtons.OK, _ MessageBoxIcon.Error) Else Try fileSize = Convert.ToInt32(item.SubItems(1).Text) Catch End Try ftp.Upload(item.Text, localCurrentDir) End If End If Else NotConnected() End If End Sub
It first checks if the application is connected to a remote server:
If ftp.Connected Then
If the application is connected, the method checks if there is a selected item in the localDirList control. If there is no item selected, it displays an error message:
Dim selectedItemCount As Integer = localDirList.SelectedItems.Count If selectedItemCount = 0 Then MessageBox.Show("Please select a file to upload.", _ "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
If there is an item selected, it gets the selected item from the localDirList control:
Dim item As ListViewItem = localDirList.SelectedItems(0)
It then checks if the item is a directory item. If it is, the method displays an error message:
If Helper.IsDirectoryItem(item) Then MessageBox.Show("You cannot upload a directory.", _ "Error uploading file", MessageBoxButtons.OK, _ MessageBoxIcon.Error)
If the selected item is a file item, it assigns the file size to fileSize and calls the FTP class's Upload method:
Try fileSize = Convert.ToInt32(item.SubItems(1).Text) Catch End Try ftp.Upload(item.Text, localCurrentDir)
You can find the source files for the application in the chapter's project directory. To compile the application, run the Build.bat file. The content of the Build.bat file is as follows:
vbc /t:library /r:System.Windows.Forms.dll Helper.vb vbc /t:library /r:System.dll,System.Windows.Forms.dll,System.Drawing.dll LoginForm.vb vbc /t:library /r:System.dll FTP.vb vbc /t:winexe /r:System.dll,System.Windows.Forms.dll, System.Drawing.dll,Helper.dll,LoginForm.dll, FTP.dll Form1.vb
|