JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

Searches

The Search class is the first of three custom-threaded objects used by FileSwapper. As part of any search, FileSwapper attempts to contact each peer with a network ping (the equivalent of asking "are you there?"). FileSwapper measures the time it takes for a response and any errors that occur, and then displays this information in the search results. This allows the user to decide where to send a download request, depending on which peer is fastest.

The drawback of this approach is that pinging each peer could take a long time, especially if some peers are unreachable. This in itself isn't a problem, provided the user has some way to cancel a long-running search and start a new one. To implement this approach, the Search class uses custom threading code.

Threading the Search class may seem easy, but it runs into the classic userinterface problem. In order to display the results in the ListView, the user-interface code must be marshaled to the main application thread using the Control.Invoke() method. This isn't difficult, but it is an added complication.

The Search class needs to track several pieces of information:

Here's a basic skeleton that shows the private variables used by the Search class:

Public Class Search

    ' The thread in which the search is executed.
    Private SearchThread As System.Threading.Thread

    ' The ListView in which results must be displayed.
    Private ListView As ListView

    Private Keywords() As String

    ' The current state.
    Private _Searching As Boolean = False
    Public ReadOnly Property Searching() As Boolean
        Get
            Return _Searching
        End Get
    End Property

    ' The search results and ping times.
    Private SearchResults() As SharedFile
    Private PingTimes As New Hashtable()

    Public Function GetSearchResults() As SharedFile()
        If _Searching = False Then
            Return SearchResults
        Else
            Return Nothing
        End If
    End Function

    Public Sub New(ByVal linkedControl As ListView)
        ListView = linkedControl
    End Sub

    ' (Other code omitted.)

End Class

The Search class code uses a thread-wrapping pattern that allows it to manage all the intricate threading details. Essentially, the Search class tracks the thread it's using and performs thread management so the rest of the application doesn't need to. The Search class provides methods such as StartSearch(), which creates and launches the thread, and Abort(), which stops the thread. This is a pattern we'll use again for the file download and upload objects.

Public Sub StartSearch(ByVal keywordString As String)
    If _Searching Then
        Throw New ApplicationException("Cancel current search first.")
    Else
        _Searching = True
        SearchResults = Nothing

        ' Parse the keywords using the same logic used when indexing files.
        Keywords = KeywordUtil.ParseKeywords(keywordString)

        ' Create the search thread, which will run the private Search() method.
        SearchThread = New Threading.Thread(AddressOf Search)
        SearchThread.Start()
    End If
End Sub

Public Sub Abort()
    If _Searching Then
        SearchThread.Abort()
       _Searching = False
    End If
End Sub

The actual searching code is contained in the private Search() method. The search results are downloaded using the shared App.SearchForFile() method, which passes the request to the discovery web service. The individual peers are pinged using a private PingRecipients() method, which makes use of a separate component. This component isn't shown here, because it requires raw socket code that's quite lengthy.

Private Sub Search()
    SearchResults = App.SearchForFile(Me.Keywords)
    _Searching = False

    PingRecipients()
    Try
        ListView.Invoke(New MethodInvoker(AddressOf UpdateInterface))
    Catch
        ' An error could occur here if the search is canceled and the
        ' class is destroyed before the invoke finishes.
    End Try
End Sub

Private Sub PingRecipients()
    PingTimes.Clear()
    Dim File As SharedFile
    For Each File In SearchResults
        Dim PingTime As Integer = PingUtility.Pinger.GetPingTime(File.Peer.IP)
        If PingTime = -1 Then
            PingTimes.Add(File.Guid, "Error")
        Else
            PingTimes.Add(File.Guid, PingTime.ToString() & " ms")
        End If
    Next
End Sub
Note 

The PingUtility uses the Internet Control Message Protocol (ICMP). As you saw in Chapter 8, not all networks allow ping requests. If a ping attempt fails, the peer's ping time will show an error, but the peer may still be reachable for a file transfer.

When the results have been retrieved and the ping times compiled, the final results are written to the ListView and the call is marshaled to the correct thread using the Control.Invoke() method.

Private Sub UpdateInterface()

    ListView.Items.Clear()
    If SearchResults.Length = 0 Then
        MessageBox.Show("No matches found.", "Error", MessageBoxButtons.OK, _
          MessageBoxIcon.Information)
    Else
        Dim File As SharedFile
        For Each File In SearchResults
            Dim Item As ListViewItem = ListView.Items.Add(File.FileName)
            Item.SubItems.Add(PingTimes(File.Guid).ToString())
            Item.SubItems.Add(File.FileCreated)
            Item.SubItems.Add(File.Peer.IP)
            Item.SubItems.Add(File.Peer.Port)
            Item.SubItems.Add(File.Guid.ToString())
            Item.SubItems.Add(File.Peer.Guid.ToString())

            ' Store the SharedFile object for easy access later.
            Item.Tag = File
        Next
    End If

End Sub

Note that the matching SharedFile object is embedded in each ListViewItem, so that it can be retrieved easily if the user chooses to download the file. This saves you from the work of creating a custom ListViewItem or parsing the text information in the ListViewItem to determine the appropriate settings.

Only one search can run at a time, because the App object provides a single Search variable. When the user clicks the Search button on the SwapperClient form, the current search is aborted immediately, regardless of its state, and a new search is launched based on the current keywords.

Private Sub cmdSearch_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles cmdSearch.Click

    If App.SearchThread.Searching Then
        App.SearchThread.Abort()
    End If

    App.SearchThread.StartSearch(txtKeywords.Text)

End Sub

Figure 9-6 shows sample search results for a query with the single word "Debussy".

Click To expand
Figure 9-6: A FileSwapper search

Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor