JavaScript Editor
JavaScript Debugger
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:
The thread it's using to execute the search.
Its current state (searching, not searching).
The ListView where it should write search results.
The SearchResults it retrieves.
The ping times it calculates.
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".
Free JavaScript Editor
JavaScript Editor