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 1
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
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"
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
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
' Convert to a byte array
Data = bArray
ReDim Data(0 To Len(sBuf))
pdUtil.BSTRToByteArray Data, 0, sBuf
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
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
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. 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
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
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