JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

Utility Functions

There are three utility classes in the FileSwapper: RegistrySettings, MP3Util, and KeywordUtil. All of them use shared methods to provide helper functions.

The first class, RegistrySettings, wraps access to the Windows registry. It allows the application to store and retrieve machine-specific information. You could replace this class with code that reads and writes settings in an application configuration file, but the drawback would be that multiple users couldn't load the same client application file from a network (as they would end up sharing the same configuration file).

The RegistrySettings class provides five settings as public variables and two methods. The Load() method retrieves the values from the specified key and configures the public variables. The Save() method stores the current values in the appropriate locations. The RegistrySettings class also hard-codes several pieces of information, including the first-run defaults (which are used if no preexisting registry information is found), and the path used for storing registry settings (HKEY_LOCAL_MACHINE\Software\FileSwapper\Settings). This information could also be drawn from an application configuration file.

Public Class RegistrySettings

    Public SharePath As String
    Public ShareMP3Only As Boolean
    Public MaxUploadThreads As Integer
    Public MaxDownloadThreads As Integer
    Public Port As Integer

    Public Sub Load()
        Dim Key As RegistryKey
        Key = Microsoft.Win32.Registry.LocalMachine.CreateSubKey( _
              "Software\FilesSwapper\Settings")
          SharePath = Key.GetValue("SharePath", Application.StartupPath)
          Port = CType(Key.GetValue("LocalPort", "8000"), Integer)
          ShareMP3Only = CType(Key.GetValue("OnlyShareMP3", "True"), Boolean)
          MaxUploadThreads = CType(Key.GetValue("MaxUploadThreads", "2"), Integer)
          MaxDownloadThreads = CType(Key.GetValue("MaxDownloadThreads", "2"), _
            Integer)
    End Sub

    Public Sub Save()
        Dim Key As RegistryKey
        Key = Microsoft.Win32.Registry.LocalMachine.CreateSubKey( _
             "Software\FilesSwapper\Settings")

        Key.SetValue("SharePath", SharePath)
        Key.SetValue("LocalPort", Port.ToString())
        Key.SetValue("OnlyShareMP3", ShareMP3Only.ToString())
        Key.SetValue("MaxUploadThreads", MaxUploadThreads.ToString())
        Key.SetValue("MaxDownloadThreads", MaxDownloadThreads.ToString())
    End Sub

End Class
Tip 

Instead of including a Load( ) and Save( ) method, you could create property procedures for the RegistrySettings class that perform this work. Then, whenever you set a property, the value will be committed, and whenever you access a value, it will be retrieved from the registry. This adds additional overhead, but it's minor.

The MP3Util class provides the functionality for retrieving MP3 tag data from a file. The class provides two shared functions. The first, GetMP3Keywords(), opens a file, looks for the 128-byte ID3v2 tag that should be found at the end of the file, and verifies that it starts with the word "TAG". If so, individual values for the artist, album, and song title are retrieved using the second method, GetTagData(), which converts the binary data to a string using ASCII encoding information. All the retrieved data is delimited with spaces and combined into along string using a StringBuilder. This string is then parsed into a list of keywords.


Public Class MP3Util

    Public Shared Function GetMP3Keywords(ByVal filename As String) As String()
        Dim fs As New FileStream(filename, FileMode.Open)

        ' Read the MP3 tag.
        fs.Seek(0 - 128, SeekOrigin.End)
        Dim Tag(2) As Byte
        fs.Read(Tag, 0, 3)

        If Encoding.ASCII.GetString(Tag).Trim() = "TAG" Then

            Dim KeywordString As New StringBuilder()
            ' Title.
            KeywordString.Append(GetTagData(fs, 30))
            ' Artist.
            KeywordString.Append(" ")
            KeywordString.Append(GetTagData(fs, 30))
            ' Album.
            KeywordString.Append(" ")
            KeywordString.Append(GetTagData(fs, 30))
            ' Year.
            KeywordString.Append(" ")
            KeywordString.Append(GetTagData(fs, 4))

            fs.Close()
            Return KeywordUtil.ParseKeywords(KeywordString.ToString())

        Else
            fs.Close()
            Dim EmptyArray() As String = {}
            Return EmptyArray
        End If
    End Function

    Public Shared Function GetTagData(ByVal stream As Stream, _
      ByVal length As Integer) As String
        Dim Bytes(length - 1) As Byte
        stream.Read(Bytes, 0, length)

        Dim TagData As String = Encoding.ASCII.GetString(Bytes)
        ' Trim nulls.
        Dim TrimChars() As Char = {" ", vbNullChar}
        TagData = TagData.Trim(TrimChars)
        Return TagData
    End Function

End Class
Note 

The GetTagData( ) includes a very important final step, which removes all null characters from the string.Without this step, the string will contain embedded nulls. If you try to submit this data to the discovery web service, the proxy class will throw an exception, because it won't be able to format the strings into a SOAP message.

The final utility class is KeywordUtil. It includes a single shared method— ParseKeywords()—that takes a string which contains a list of keywords, and splits it into words wherever a space, comma, or period is found. This step is performed using the built-in String.Split() method. Thus, if you index an MP3 file that has the artist "Claude Debussy," the keyword list will include two entries: "Claude" and "Debussy". This allows a peer to search with both or only one of these terms.

At the same time that ParseKeywords() splits the keyword list, it also removes extraneous strings, such as noise words ("the", "for", "and", and so on). You may want to add additional noise words to improve its indexing. In addition, strings that include only a delimiter are removed (for example, a string containing a single blank space). This is necessary because the String.Split() method doesn't deal well with multiple spaces in a row. To make the processing logic easy, keywords are added into an ArrayList on the fly and converted into a strongly typed string array when the process is complete.

Public Class KeywordUtil

    Private Shared NoiseWords() As String = {"the", "for", "and", "or"}
    Public Shared Function ParseKeywords(ByVal keywordString As String) _
      As String()
        ' Split the list of words into an array.
        Dim Keywords() As String
        Dim Delimeters() As Char = {" ", ",", "."}
        Keywords = keywordString.Split(Delimeters)
        ' Add each valid word into an ArrayList.
        Dim FilteredWords As New ArrayList()
        Dim Word As String
        For Each Word In Keywords
            If Word.Trim() <> "" And Word.Length > 1 Then
                If Array.IndexOf(NoiseWords, Word.ToLower()) = -1 Then
                    FilteredWords.Add(Word)
                End If
            End If
        Next

        ' Convert the ArrayList into a normal string array.
        Return FilteredWords.ToArray(GetType(String))
    End Function

End Class

Thread-Safe ListViewItem Updates

The FileSwapper is a highly asynchronous application that provides real-time status information for many tasks. In several places in code, a user-interface operation needs to be marshaled to the user-interface thread in order to prevent potential errors. This is usually the case when updating one of the three main ListView controls in the FileSwapper: the upload status display, the download status display, and the search-result listing.

For the first two cases, there's a direct mapping between threads and ListView items. For example, every concurrent upload requires exactly one ListViewItem to display ongoing status information. To simplify the task of creating and updating the ListViewItem, FileSwapper includes a wrapper class called ListViewItemWrapper. ListViewItemWrapper performs two tasks. When it's first instantiated, it creates and adds a ListViewItem on the correct thread using the private AddListViewItem() procedure. Second, when a user calls the ChangeStatus() method, it updates the status column of a ListViewItem on the correct thread using the private RefreshListViewItem() procedure. In order to use these subroutines with the Control.Invoke() method, they cannot take any parameters. Thus, the information required to create or update the ListViewItem must be stored in temporary private variables, such as RowName and RowStatus.

Here's the complete code for the ListViewItemWrapper:


Public Class ListViewItemWrapper

    Private ListView As ListView
    Private ListViewItem As ListViewItem

    ' These variables are used to store temporary information required when a call
    ' is marshaled to the user-interface thread.
    Private RowName As String
    Private RowStatus As String

    Public Sub New(ByVal listView As ListView, ByVal rowName As String, _
      ByVal rowStatus As String)
        Me.ListView = listView
        Me.RowName = rowName
        Me.RowStatus = rowStatus

        ' Marshal the operation to the user-interface thread.
        listView.Invoke(New MethodInvoker(AddressOf AddListViewItem))
    End Sub

    ' This code executes on the user-interface thread.
    Private Sub AddListViewItem()
        ' Create new ListView item.
        ListViewItem = New ListViewItem(RowName)
        ListViewItem.SubItems.Add(RowStatus)
        ListView.Items.Add(ListViewItem)
    End Sub

    Public Sub ChangeStatus(ByVal rowStatus As String)
        Me.RowStatus = rowStatus

        ' Marshal the operation to the user-interface thread.
        ListView.Invoke(New MethodInvoker(AddressOf RefreshListViewItem))
    End Sub

    ' This code executes on the user-interface thread.
    Private Sub RefreshListViewItem()
        ListViewItem.SubItems(1).Text = RowStatus
    End Sub

End Class

The ListViewItemWrapper is a necessity in our peer-to-peer application, because the downloading and uploading operations won't be performed on the main application threads. However, you'll find that this class is useful in many Windows applications. Any time you need to create a highly asynchronous interface, it makes sense to use this control wrapper design pattern.


Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor