WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Building Palm Conduits, Part 3
Pages: 1, 2, 3, 4

At this point, all the changed records from the Palm device are safely written to the desktop folder. As we'll see later, DirtyRec and DeletedRec take care of any conflicts between desktop and Palm device records. Now FastSync needs to write any changed data from the desktop to the Palm database.



FastSync loops through the files on the desktop, looking for those with extensions that require some processing. For each file found, it calls a routine to do the actual work, supplying the file object, the record adapter, and the utility object as reference parameters:


Select Case UCase(FSO.GetExtensionName(File.Name))
Case "NEW"
    NewFile DBPath, File, pdRecords, pdUtil
Case "DEL"
    DeletedFile File, pdRecords, pdUtil
Case "CHG"
    DirtyFile DBPath, File, pdRecords, pdUtil
End Select

The last thing the FastSync routine has to do is to clean up deleted records on the Palm device, and clear the change bit(s). The data is now synchronized, so nothing is dirty! FastSync uses the same calls we saw in HHtoPC to do this cleanup work.

Now that the top-level structure of FastSync is clear, let's look at the auxiliary functions that move the bits and bytes. The implementation of DirtyRec is shown in Example 4-11. To understand its logic, recall that in our conduit, a change to a Palm record has precedence over a change to the corresponding desktop record.

In This Series

Building Palm Conduits, Part 4
This final book excerpt in this series from Programming Palm OS with Visual Basic covers data formats, and packing and unpacking record data in a Palm application.

Building Palm Conduits, Part 2
In this excerpt from Programming Visual Basic for the Palm OS, learn how to design conduits using VB for the Palm OS.

Building Palm Conduits, Part 1
This excerpt from Programming VB for PalmOS, offers an introduction to building conduits, synchronization software that connects Palm apps and data stores.

Example 4-11: Listing for SyncLogic.DirtyRec


Private Sub DirtyRec(ByVal DBPath As String, _
                     ByVal strID As String, _
                     ByRef Data As Variant)
 
    Dim Filenum As Integer
    Dim Filename As String
    Dim FSO As New FileSystemObject
    
    ' Remove any changed or deleted desktop record
    On Error Resume Next
    FSO.DeleteFile DBPath + "\" + strID + ".DEL", True
    FSO.DeleteFile DBPath + "\" + strID + ".CHG", True
    On Error GoTo 0
    
    ' Write device data to desktop record
    Filenum = FreeFile
    Filename = DBPath + "\" + strID + ".REC"
    Open Filename For Output As #Filenum
    Write #Filenum, StrConv(Data, vbUnicode)
    Close #Filenum
    
End Sub

Because changes to desktop records are stored in files with the extension .DEL or .CHG, DirtyRec simply removes those files. Then the contents of the Palm record are written into the desktop file. This process overwrites the old desktop record, if it existed, or creates a new record file with the correct name and extension.

This is in accordance with our design decision that changes to the Palm record have precedence over the desktop. A conduit that implemented Palm's recommended mirroring strategy would have to reconcile the contents of the files with the Palm record data.

The implementation of DeletedRec is very similar.

Private Sub DeletedRec(ByVal DBPath As String, _
                       ByVal strID As String, _
                       ByRef Data As Variant)
                       
    Dim FSO As New FileSystemObject
    
    On Error Resume Next
    FSO.DeleteFile DBPath + "\" + strID + ".REC", True
    On Error GoTo 0
 
End Sub

However, note that DeletedRec does not remove the .CHG record, which gets processed later. This is because a change on the desktop has precedence over deletions on the Palm device. If this seems unclear, look over Table 4-14 again.

The NewFile function creates a new record in the Palm database when the user has created one on the desktop:

Private Sub NewFile(ByVal DBPath As String, _
                    ByRef File As File, _
                    ByRef pdRecords As PDRecordAdapter, _
                    ByRef pdUtil As PDUtility)
 
    Dim Data As Variant
    Dim RecordId As Variant
 
    GetFileContents File, Data, pdUtil
    
    ' Create a new Palm device record
    RecordId = vbEmpty
    pdRecords.Write RecordId, 0, 0, Data
    
    File.Move DBPath + "\" + pdUtil.RecordIdToString(RecordId) + ".REC"
 
End Sub

Despite its simplicity, there is a lot going on in NewFile. First, the routine calls GetFileContents to read the file data into a variant byte array for uploading to the Palm database record. We'll see how this is done later.

Next, we create a new record in the Palm database. The PDRecordAdapter class doesn't have an explicit record creation method; instead, you call its Write function with a special record identifier. Passing a variant set to vbEmpty does the trick. When the Write function returns, it has replaced vbEmpty with the new Record ID.

TIP:   It is not always possible to create a record on the device--for example the storage heap could be exhausted. We don't handle that error in our simple conduit, but your conduit should.

The last thing NewFile does is to rename the .NEW desktop file so we don't process it again later. We generate a filename using the new Record ID and an extension of .REC. The records are now synchronized on the desktop and the device. If the user later changes this record on the Palm, our conduit will be able to locate the corresponding desktop file using the Record ID as filename.

Now let's look at GetFileContents, shown in Example 4-12. Reading in the file contents is simple enough; the routine assumes all the text is a single input field delimited by quotation marks, and reads it into the string variable sBuf.

Example 4-12: Listing for SyncLogic.GetFileContents

Private Sub GetFileContents(ByRef File As File, _
                            ByRef Data As Variant, _
                            ByRef pdUtil As PDUtility)
 
    Dim Filenum As Integer
    Dim sBuf As Variant
    Dim RecordId As Variant
    Dim bArray(  ) As Byte
 
    Filenum = FreeFile
    Open File.Path For Input As #Filenum
    Input #Filenum, sBuf
    Close #Filenum
 
    ' Convert to a byte array
    Data = bArray
    ReDim Data(0 To Len(sBuf))
    pdUtil.BSTRToByteArray Data, 0, sBuf
    
End Sub

Next, we convert the input string, sBuf (which may or may not be Unicode, depending on your operating system), into a byte array. To do this, declare an empty byte array and assign the reference parameter Data to it:

Dim bArray(  ) As Byte
...
Data = bArray

This effectively converts Data, which is a type-less variant, into a byte array. Re-dimension Data to hold the input string, and use the utility function BSTRToByteArray to pack the string data into the array:


ReDim Data(0 To Len(sBuf))
pdUtil.BSTRToByteArray Data, 0, sBuf

We resort to this trickery because you cannot pass a VB byte array directly into a COM function call. If your conduit's data is more complicated, you should look at the other conversion functions in PDUtility.

The conduit calls DeletedFile to handle desktop files that the user has marked for deletion. This function is very straightforward: convert the desktop filename into a Palm Record ID using the StringToRecordId utility function, and then call the PDRecordAdapter Remove function to erase the database record. Here's the code for DeletedFile:


Private Sub DeletedFile(ByRef File As File, _
                        ByRef pdRecords As PDRecordAdapter, _
                        ByRef pdUtil As PDUtility)

    Dim RecordId As Variant
    Dim FSO As New FileSystemObject

    RecordId = pdUtil.StringToRecordId(FSO.GetBaseName(File.Name))
    On Error Resume Next
    pdRecords.Remove RecordId
    File.Delete True
    On Error GoTo 0
    
End Sub

We wrap the actual Remove call in an error handler, because it raises a runtime error if the requested record does not exist. This is an unlikely condition in a well-designed application, but it happens frequently during development. A simple On Error Resume Next ensures that we handle that possibility.

The conduit calls DirtyFile to handle desktop files that the user has changed. The code for DirtyFile is shown in Example 4-13. This routine repackages some functionality we have seen earlier. It calls GetFileContents to read in the changed desktop data, and builds a Palm Record ID using the StringToRecordId utility function.

Example 4-13: Listing for SyncLogic.DirtyFile


Private Sub DirtyFile(ByVal DBPath As String, _
                      ByRef File As File, _
                      ByRef pdRecords As PDRecordAdapter, _
                      ByRef pdUtil As PDUtility)

    Dim Data As Variant
    Dim RecordId As Variant
    Dim FSO As New FileSystemObject

    GetFileContents File, Data, pdUtil
    
    ' Find correct Palm device record based on file name
    RecordId = pdUtil.StringToRecordId(FSO.GetBaseName(File.Name))

    On Error Resume Next
    pdRecords.Write RecordId, 0, eDirty, Data
    If Err.Number <> 0 Then
        ' Record deleted on device without warning.
        RecordId = vbEmpty
        pdRecords.Write RecordId, 0, eDirty, Data
    End If
    On Error GoTo 0
    
    ' Rename from .CHG to .REC
    File.Move DBPath + "\" + pdUtil.RecordIdToString(RecordId) + ".REC"

End Sub

If DirtyFile encounters an error when updating the Palm database, it assumes that the record no longer exists. In this case, DirtyFile creates a new record by writing the data using a Record ID of vbEmpty. As we mentioned before, this is an unlikely condition, but you should take great care to make your conduit very robust. Note that the original Record ID is lost.

As usual, we rename the desktop file to have the .REC extension. Note the use of the eDirty attribute when writing the record. Assigning this attribute overwrites any other Palm record attributes, including eDelete. When FastSync cleans up the Palm database by purging deleted records, these dirty records won't be among them.

Other Sync Types

In contrast to fast synchronization, slow synchronization requires looking at all records, not just those that are marked as dirty or new. In our simple application, we just had to change the PDRecordAdapter iterator function--for example, ReadNext instead of ReadNextModified. This causes SlowSync to look at every record in the Palm database, not simply the changed ones.

Particular care must be taken if you expect your users to synchronize their application data with different desktops or devices. When that happens, it is easy to lose track of data--usually with very bad results for your users.[5] Design carefully to avoid this.

For the sake of completeness, we support the PCtoHH sync type in the sample application. There is nothing noteworthy in the code that we haven't already covered, so we won't detail it here.

Running the Conduit

At this point, you have seen all the code in the sample conduit. You can compile it as an ActiveX EXE, and register it with the HotSync manager using the CondCfg.exe tool covered earlier in this chapter (see Figure 4-4). Instead of the VB IDE, enter the programmatic identifier of the conduit as the COM client. In the case of our sample, this is Ch4aCond.SyncNotify.

To debug, stop the HotSync manager, and then run the conduit from the VB IDE. Make sure you have enabled the default debug setting: Wait for components to be created. You do this from the VB IDE by choosing the Properties option from the Project menu, and then selecting the Debugging tab.

Next, set a breakpoint in each of the routines for IPDClientNotify, and then press F5 to run the project. This generates a new temporary GUID for your public class, but the programmatic id stays the same. Once the project is running, restart the HotSync manager.

The debugger should launch into the breakpoint in GetConduitInfo first, because the HotSync manager checks every registered conduit as it initializes. The HotSync manager will call this function several times, once for each information request type.

You can trigger the other breakpoints by choosing the Custom option for our conduit from the HotSync manager user interface (in the Windows system tray), or by actually performing a HotSync with the device in the cradle.

Test the conduit by using the Palm application Ch4a.prc to manipulate records on the Palm device, and a text editor to edit files on the desktop. Then synchronize with the HotSync manager.

Roger Knoell s a software developer with 10 years experience leveraging high-level language development tools and environments.

Patrick Burton has been programming in C/C++ for most of his career. His experience inlcludes algorithm development for embedded satellite receivers, Linux system programming, and Windows programming using the Win32 API and Microsoft Found Classes (MFC).

Matthew Holmes has been developing computer software for 15 years. He cherishs his liberal arts degree in Foreign Affairs from the University of Virginia, and he holds a graduate degree in Computer Science from George Mason University.

In the last installment, learn about data formats.

Return to .NET DevCenter