Synchronization Logic

The SyncLogic object created in the BeginProcess routine encapsulates all of our synchronization logic. In our example, the class instancing property is set to Private, and the class file is saved as Ch4aLogic.cls. By partitioning a conduit in this fashion, between the public interface and the internal synchronization logic, we ensure that the interface code can be reused in other conduits.

Let’s look at the Synchronize method, which is called to handle all of the synchronization requests made of this conduit. The code for Synchronize is shown in Example 4-8.

Example 4-8. Listing for SyncLogic.Synchronize

Public Sub Synchronize(  )

    ' Get the HotSync information object for this conduit
    Dim pdSys As New PDSystemAdapter
    Dim pdHSInfo As PDHotsyncInfo
    Set pdHSInfo = pdSys.PDHotsyncInfo

    ' Route the requested synchronization to the local handler
    Select Case pdHSInfo.SyncType
        Case eFast
            FastSync pdSys, pdHSInfo
        Case eSlow
            SlowSync pdSys, pdHSInfo
        Case eHHtoPC
            HHtoPC pdSys, pdHSInfo
        Case Else
            LogSync pdSys, pdHSInfo.SyncType
    End Select

End Sub

The first thing the method does is to declare and initialize a Palm Sync Suite COM object:

Dim pdSys As New PDSystemAdapter

The PDSystemAdapter class represents the Palm device. This powerful class provides access to most features of the device (except databases, which are handled by a separate class). The PDSystemAdapter class has the methods and properties shown in Table 4-10.

Table 4-10. Properties and methods of PDSystemAdapter

Property or method name

Description

AddLogEntry

Makes a local or device HotSync log entry

CallRemoteModule

Runs a program on the device

DateTime

Retrieves the date/time on the device

HHOsVersion

Retrieves the device operating system version

LocalizationID

Retrieves the device localization setting

PDHotSyncInfo

Object representing current HotSync

PDMemoryCardInfo

Object representing device memory

PDUserInfo

Object representing current user

ProductId

Retrieves the device product ID

ReadAppPreference

Retrieves a device application setting

ReadFeature

Retrieves device feature memory

RebootSystem

Performs a device soft-reset

RomSoftwareVersion

Retrieves the device ROM version

SyncManagerAPIVersion

Retrieves HotSync API Version

WriteAppPreference

Stores an application setting on device

The PDSystemAdapter and its subobjects exist only when synchronization is actually occurring. You cannot create this object, or its subobjects, outside the scope of BeginProcess. Our example conduit uses only a fraction of PDSystemAdapter’s features; you can explore the CDK samples to see how to use the other features.

If you look at the Sync Suite class hierarchy shown earlier in Figure 4-5, you see that one of the subobjects of the system adapter is PDHSInfo. This object represents the current HotSync session. From it, we can get the synchronization type that the HotSync manager wants our conduit to run:

Dim pdHSInfo As PDHotsyncInfo
Set pdHSInfo = pdSys.PDHotsyncInfo

Note that the PDHSInfo class is not a publicly creatable object. You must use the system adapter to get one, as shown. The PDHSInfo class has the methods and properties shown in Table 4-11.

Table 4-11. Properties and methods of PDHSInfo

Property or method name

Description

CardNum

Memory card on device for this application

ConnectionType

Indicator of local, modem, or network connection

Creator

Application Creator ID for this conduit

DbType

Database type for this conduit

FirstSync

Indicator of first synchronization for device or desktop

LocalName

Application name on device

NameList

Database(s) for this Creator ID on device

PathName

Path for user area in HotSync directory

RegistryKey

Registry key for this conduit

RegistryPath

Registry path for this conduit

RemoteNameCount

Number of databases for this Creator ID on device

SyncType

Type of synchronization to perform

UserName

Username on device for this conduit

As with PDSystemAdapter, we use only a couple of features from the PDHSInfo object. While our sample conduit only has one database, the HotSync manager provides your conduit with a list of all remote databases that belong to your application. (Even though a conduit might be responsible for synchronizing more than one database, it can only have one open at a time. This complicates the design if the conduit needs to enforce relationships between the databases (tables)).

We use the SyncType object property to route program flow to the function that handles the requested synchronization type, supplying the newly created COM objects as reference parameters:

Select Case pdHSInfo.SyncType
    Case eFast
        FastSync pdSys, pdHSInfo
    Case eSlow
        SlowSync pdSys, pdHSInfo
    Case eHHtoPC
        HHtoPC pdSys, pdHSInfo
    Case ePCtoHH
        PCtoHH pdSys, pdHSInfo

Because this conduit only supports Fast, Slow, HHtoPC and PCtoHH synchronization, we direct all other synchronization types to a function that simply logs the request:

    Case Else
        LogSync pdSys, pdHSInfo.SyncType
End Select

We won’t spend any time looking at the code for LogSync; it consists of a large select statement that builds a string identifying the conduit and synchronization type, and then uses the pdSystemAdapater.AddLogEntry method to write the entry to the HotSync log:

pdSys.AddLogEntry "Ch4a - " + strType, eText, False, False

Calling AddLogEntry with an option other than eText, such as eWarning, causes the HotSync manager to alert the user after all conduits have finished executing, as shown in Figure 4-8. See the enumeration type ElogActivitity for the supported log types.

HotSync warning dialog

Figure 4-8. HotSync warning dialog

The AddLogEntry method supports writing to either the desktop or the device HotSync log. Set the optional fourth parameter to True to write to the device. Take care when writing to the device log to keep the amount of information to a minimum.

That wraps up the high-level presentation of the Synchronize object: we’ve seen how it is created, how it routes HotSync commands to the correct internal functions, and how it logs information to the HotSync log. Next, we are going to look at the low-level synchronization functions HHtoPC and FastSync.

HHtoPC

In our simple conduit, the HHtoPC routine is called only at the user’s request. The purpose of this routine couldn’t be simpler: it deletes all records from the desktop and then copies any records from the Palm device. The code for HHtoPC is shown in Example 4-9.

Example 4-9. Listing for SyncLogic.HHtoPC

Private Sub HHtoPC(ByRef pdSys As PDSystemAdapter, _
                   ByRef pdHSInfo As PDHotsyncInfo)

    ' Data is under user's directory in HotSync area.
    Dim DBPath As String
    DBPath = pdHSInfo.PathName + "Ch4a"
    
    ' Purge the PC data - force a new directory if necessary.
    Dim FSO As New FileSystemObject
    On Error Resume Next
    FSO.DeleteFile DBPath + "\*.*", True
    FSO.CreateFolder DBPath
    On Error GoTo 0
    
    ' Get the Palm database from the HotSync manager
    Dim DBName As String
    DBName = pdHSInfo.NameList(0)
    
    Dim pdQuery As New PDDatabaseQuery
    Dim pdRecords As PDRecordAdapter
    Set pdRecords = pdQuery.OpenRecordDatabase(DBName, "PDDirect.PDRecordAdapter")
    
    ' Open the handheld database, and iterate over the records
    Dim Index As Long
    Dim RecordId As Variant
    Dim Category As Long
    Dim Attributes As ERecordAttributes
    Dim Data As Variant

    pdRecords.IterationIndex = 0
    Data = pdRecords.ReadNext(Index, RecordId, Category, Attributes)
    Do While Not pdRecords.EOF
    
        ' In a HHtoPC sync, process all but deleted records
        If Not CBool(CByte(Attributes) And CByte(eDelete)) Then
            WriteRecContents DBPath, RecordId, Data
        End If
 
        ' Read the next record and skip to the top of the loop
        Data = pdRecords.ReadNext(Index, RecordId, Category, Attributes)
    Loop
    
    '  Remove any deleted records, clear  flags in Palm database
    pdRecords.RemoveSet eRemoveAllDeletedRecords
    pdRecords.ResetAllModifiedFlags
    
    LogSync pdSys, pdHSInfo.SyncType

End Sub

Let’s examine the HHtoPC routine a little bit at a time. First, the routine locates the desktop data store, which is located under the user’s Palm desktop directory. It is customary for Palm conduits to keep their information in this directory. The directory is available as the PathName method of the PDHSInfo object; the routine tacks on a folder name to create a subdirectory.

Dim DBPath As String
DBPath = pdHSInfo.PathName + "Ch4a"

The routine then deletes any files in that folder. Wrapping the delete operation in an error handler is necessary, because the FileSystemObjectthrows an error if the folder doesn’t exist or if it is empty.

Dim FSO As New FileSystemObject
On Error Resume Next
FSO.DeleteFile DBPath + "\*.*", True
FSO.CreateFolder DBPath

Next, HHtoPC creates an instance of the Sync Suite PDDatabaseQuery class:

Dim pdQuery As New PDDatabaseQuery

This class provides programmatic access to the Palm database manager on the device. Remember, instances of this class are only available when the user is synchronizing the device, not during conduit configuration. This class has methods and properties, as shown in Table 4-12, that allow us to manage Palm databases.

Table 4-12. Properties and methods of PDDatabaseQuery

Property or method name

Description

AddLogEntry

Makes an entry in either the desktop or device HotSync log.

CreateRecordDatabase

Creates a data type database. Records are unstructured.

CreateResourceDatabase

Creates a resource-type database. Each record has a structure, such as an icon or form.

MaxAllowedRecordSize

Retrieves maximum supported record size on the device.

OpenRecordDatabase

Opens an existing data type database.

OpenResourceDatabase

Opens an existing resource-type database.

RamDbCount

Retrieves number of databases in device RAM.

ReadDbInfoByCreatorType

Retrieves statistics and settings for a database.

ReadDbInfoByName

Retrieves statistics and settings for a database.

ReadDbNameList

Retrieves list of databases in RAM or ROM.

RemoveDatabase

Deletes a RAM or ROM database.

RomDbCount

Retrieves number of databases in device ROM.

Again, look at the class hierarchy shown earlier in Figure 4-5. PDDatabaseQuery is a publicly created object. Our main interest in this class is its ability to return objects representing actual databases on the Palm device. We get the name of our database from the PDHSInfo object:

' Get the Palm database from the HotSync manager
Dim DBName As String
DBName = pdHSInfo.NameList(0)

Note that NameList is an array. If your application has more than one database on the Palm PDA, each is listed in the array. The total number of databases is available in the RemoteNameCount property.

The Sync Suite API provides the PDRecordAdapter to access the contents of any one database on the Palm device. This object is created in an unusual way, by passing the programmatic identifier of a class factory into the database query object.

The class factory is responsible for producing an object that satisfies all the interface requirements for an instance of PDRecordAdapter. The CDK provides this unusual construction technique so developers can subclass the record adapter, and supply extra capabilities tailored for a specific application. In our conduit, we use the default record adapter supplied by Palm:

Dim pdRecords As PDRecordAdapter
Set pdRecords = pdQuery.OpenRecordDatabase(DBName, "PDDirect.PDRecordAdapter")

Table 4-13 shows the many methods and properties of PDRecordAdapter. This large class is heavily used in our sample conduit. In the table, two sets of functions have been grouped together: direct record access, denoted in the table by ReadBy*, and iterator access, denoted in the table by ReadNext*. The direct access functions allow the retrieval of a single record, either by index or record identifier. The iterator access functions allow the sequential retrieval of many records, either by index or by category or other attribute.

Table 4-13. Properties and methods of PDRecordAdapter

Property or method name

Description

AccessMode

Retrieves mode(s) used to open database

AddLogEntry

Makes an entry in either the desktop or device HotSync log

ChangeCategory

Changes the Category ID for a group of records

CloseOptions

Sets modification date/time on database prior to close

DbName

Retrieves the database name

EOF

Indicates end-of-file when using an iterator

InputBufferSize

Sets the maximum size for read/write buffers

IterationIndex

Sets the start offset in the database for an iterator

PDCategories

Represents category data for this database

PDDatabaseInfo

Represents database for this record adapter

ReadAppInfoBlock

Reads application-specific data, including categories

ReadBy*

Gets record information by index or identifier

ReadNext*

Gets record information from an iterator

ReadSortInfoBlock

Retrieves application-specific data, notionally used for sorting

ReadUniqueIdList

Retrieves list of record identifiers in database

RecordCount

Retrieves count of records in database

Remove

Permanently erases a record from database

RemoveSet

Permanently erases a group of records from database

ResetAllModifiedFlags

Clears the dirty bit for all records in database

Write

Creates or updates a database record and attributes

WriteAppInfoBlock

Writes application-specific data, including categories

WriteSortInfoBlock

Writes application-specific data, notionally used for sorting

All the iterator functions support a starting index position. In HHtoPC, we use the simple iterator to process all the records in the database, starting at index zero, and reading both the actual data and other record attributes:

pdRecords.IterationIndex = 0
Data = pdRecords.ReadNext(Index, RecordId, Category, Attributes)
Do While Not pdRecords.EOF

All of the PDRecordAdapter read functions return a VB variant, with the record data actually stored in a byte array. The read functions also set an attribute byte indicating the status of the current record. If the record is not marked for deletion, we copy it to the desktop by calling WriteRecContents.

If Not CBool(CByte(Attributes) And CByte(eDelete)) Then
    WriteRecContents DBPath, RecordId, Data
End

If your conduit supports record archival, then you should test for that condition as well, with the eArchive attribute. After we have processed the record, we read the next record, and skip to the top of the loop:

    Data = pdRecords.ReadNext(Index, RecordId, Category, Attributes)
Loop

Eventually, the read function will cause the record adapter to reach the end-of-file condition and we will exit the loop after all of the database records have been processed. HHtoPC then purges the Palm database of any logically deleted records, and clears the modification flag for all dirty records:

pdRecords.RemoveSet eRemoveAllDeletedRecords
pdRecords.ResetAllModifiedFlags

Now let’s look at how the WriteRecContents routine stores the Palm device record data to a desktop file. We aren’t going to show all the code in WriteRecContents, but just the highlights. This routine uses a new Sync Suite object, PDUtility, to transform the Palm Record ID into a string:

Dim pdUtil As New PDUtility
Filename = DBPath + "\" + pdUtil.RecordIdToString(RecordId) + ".REC"

The Palm CDK documentation strongly encourages developers to use this function, rather than dissecting the variant data type holding a Record ID. This is because the Record ID format, currently a long integer, may change in the future. The PDUtility class has other methods to convert data between the Palm and desktop formats. We will see some of these as we pack and unpack string data for our records.

This completes the presentation of the HHtoPC synchronization logic for our simple conduit. Although this section presented a great deal of information quickly, you should now have an appreciation of how to use the Sync Suite COM objects to access features and data on the Palm device.

FastSync

The HHtoPC sync didn’t require much of a design—just take the Palm records and write them to the desktop, removing any existing desktop records in the process. Mirror synchronization is much harder.

Before we start into coding the FastSync, let’s reexamine what mirror synchronization means for the Ch4a application. In Table 4-14, we’ve recast our table of possibilities to include the desktop file extensions instead of attribute flags. The code for our conduit has to identify all these possibilities, and then take the action indicated in the table. Preparing a table or state diagram like this when designing your conduit will prove helpful.

Table 4-14. Ch4a conduit actions

No record

No change

.CHG

.NEW

.DEL

No record

D P

No change

D P

Remove DP

Change

P D

Conflict

P D

New

P D

Delete

Remove DP

D P

In this simple conduit, the change conflict is handled in a straightforward manner: changes to a record on the Palm device take precedence over changes to the same desktop record. This is different from the Palm CDK recommendation of creating two new records, one on each platform that mirrors the changes. Of course, you will have to decide how to resolve any conflict in a fashion that is appropriate for your application.

Now let’s look at FastSync, the low-level function that actually moves the data between the Palm device and the Windows desktop. It is shown in Example 4-10.

Example 4-10. Listing for SyncLogic.HHtoPC

Private Sub FastSync(ByRef pdSys As PDSystemAdapter, _
                     ByRef pdHSInfo As PDHotsyncInfo)
        
    Dim Index As Long
    Dim Category As Long
    Dim Data As Variant
    Dim RecordId As Variant
    Dim Attributes As ERecordAttributes

    ' Data is under user's directory in HotSync area.
    Dim DBPath As String
    DBPath = pdHSInfo.PathName + "Ch4a"
    
    ' Get Palm database name from HotSync manager
    Dim DBName As String
    DBName = pdHSInfo.NameList(0)

    ' Open the handheld database, and get the record interface.
    Dim pdQuery As New PDDatabaseQuery
    Dim pdRecords As PDRecordAdapter
    Set pdRecords = pdQuery.OpenRecordDatabase(DBName, "PDDirect.PDRecordAdapter")
    
    ' Iterate over the *modified* records
    pdRecords.IterationIndex = 0
    Data = pdRecords.ReadNextModified(Index, RecordId, Category, Attributes)
    Do While Not pdRecords.EOF
    
        Dim strID As String
        Dim pdUtil As New PDUtility
        strID = pdUtil.RecordIdToString(RecordId)
        
         If CBool(CByte(Attributes) And CByte(eDelete)) Then
            DeletedRec DBPath, strID, Data
        Else
            DirtyRec DBPath, strID, Data
        End If
        
        ' Get the next record
        Data = pdRecords.ReadNext(Index, RecordId, Category, Attributes)
    Loop

    Dim File As File
    Dim Folder As Folder
    Dim FSO As New FileSystemObject
    
    Set Folder = FSO.GetFolder(DBPath)
    For Each File In Folder.Files
        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
    Next

    pdRecords.RemoveSet eRemoveAllDeletedRecords
    pdRecords.ResetAllModifiedFlags
    
    LogSync pdSys, pdHSInfo.SyncType

End Sub

We won’t go over every line, as we have seen a lot of this code already in HHtoPC. The routine consists of some setup code, and then two main loops. The first loop pulls changes from the Palm device, and the second loop writes changes to the Palm device.

One large difference between FastSync and HHtoPC is that with FastSync, we only want to process records that have changed since the last synchronization. Remember, this is the definition of a FastSync. The PDRecordAdapter provides the ReadNextModified iterator that is specially designed for this circumstance. Each call to this iterator skips through the Palm database, returning only the changed records. As a side effect, the index variable will be incremented, not by one, but by however many records the iterator skipped over to find the next changed record.

Data = pdRecords.ReadNextModified(Index, RecordId, Category, Attributes)

In a Palm database, it is possible for a record to be both deleted and dirty at the same time. Actually, it can be archived as well, but remember that we don’t support the Palm archive attribute.

A Palm application usually asks the user if the deleted record should be archived or simply removed from the database. Deleted records have no data, but they are still present in the physical database; you test for them using the eDelete attribute.

If CBool(CByte(Attributes) And CByte(eDelete)) Then
    DeletedRec DBPath, strID, Data
Else
    DirtyRec DBPath, strID, Data
End If

Archived records have data in order to support the archive operation; typically, the archived record is also marked for deletion.

Tip

If your application uses the AppForge database library, call the extended delete function PDBDeleteRecordEx to mark a record as both archived and deleted. You do this by passing afDeleteModeArchive to the function.

For our conduit, there is no difference between a new record and a changed record; both are dirty relative to the desktop. Note that the CDK doesn’t provide a method to distinguish the two cases (there is no eNew attribute). You can tell, of course, because a new Palm database record won’t have a corresponding desktop file.

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.

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.[31] 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.



[31] The slow sync logic given here fails to handle this important case. If the user syncs with his desktop, then does a delete on the handheld, and then syncs with another desktop, the deleted record on the handheld is gone. Now, if the user syncs with his desktop, the conduit doesn’t see the deleted record on the handheld, and so it shouldn’t delete it on the desktop. Whew. You’ll need to iterate through the records on the desktop. Any that aren’t on the handheld (and aren’t new) have been deleted, and must be deleted from the desktop (unless, of course, they’ve been modified on the desktop).

Get Programming Visual Basic for the Palm OS now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.