Implementing the Project


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.

click to expand
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.

click to expand
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.

Creating the Class Diagram

Figure 5-4 shows the class diagram for this application.

click to expand
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.

Creating the Helper Class

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 Helper Class's Methods

The following sections describe the two methods of the Helper class.

IsDirectory

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 

IsDirectoryItem

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 

Creating the LoginForm Class

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 

Creating the FTP Class

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's Declaration

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's Properties

The FTP class has two read-only properties.

Connected

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 

DirectoryList

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 FTP Class's Methods

The following sections describe the methods in the FTP class.

ChangeDir

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 

ChangeToAsciiMode

The ChangeToAsciiMode method changes the transfer mode to ASCII:

 Public Sub ChangeToAsciiMode()   SendTYPECommand("A") End Sub 

Connect

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 

DeleteDir

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 

DeleteFile

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 

Disconnect

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 

DoDownload

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 

DoUpload

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 

Download

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

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 

GetResponse

The GetResponse method receives the server reply and is the same as the GetResponse method in the NETFTP class.

Login

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 

MakeDir

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 

PassiveDataConnection

The PassiveDataConnection method is the same as the PassiveDataConnection method in the NETFTP class.

Rename

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 

SendCommand

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 

SendTYPECommand

The SendTYPECommand method is the same as the SendTYPECommand method in the NETFTP class.

Upload

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's Events

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) 

Creating the EventArgs Subclasses

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 

Creating the Form1 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.

click to expand
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's Declaration

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 Form1 Class's Methods

The following sections describe the Form1 class's methods.

ChangeLocalDir

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() 

ChangeRemoteDir

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() 

Connect

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.") 

DeleteLocalFile

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() 

DeleteRemoteFile

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 

DownloadFile

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) 

GetDirectoryItem

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 

InitializeProgressBar

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 

LoadLocalDirList

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 

LoadRemoteDirList

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 

Log

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 

MakeLocalDir

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() 

MakeRemoteDir

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 

NotConnected

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 = "" 

RenameLocalFile

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() 

RenameRemoteFile

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) 

SelectLocalDirectory

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 

SelectRemoteDirectory

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 

UpdateLocalDir

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 

UpdateProgressBar

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 

UpdateRemoteDir

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.

UploadFile

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) 

Compiling and Running the Application

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 




Real World. NET Applications
Real-World .NET Applications
ISBN: 1590590821
EAN: 2147483647
Year: 2005
Pages: 82

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net