|
Related Reading
Programming Visual Basic for the Palm OS |
In this excerpt from Programming Visual Basic for Palm OS, learn about synchronization logic. Part 3 of 4.
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.
|
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.
|
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 |
|
In This Series
Building Palm Conduits, Part 4
Building Palm Conduits, Part 2
Building Palm Conduits, Part 1 |
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.
|
|
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.
|
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 StringDBPath = pdHSInfo.PathName + "Ch4a"' Purge the PC data - force a new directory if necessary.Dim FSO As New FileSystemObjectOn Error Resume NextFSO.DeleteFile DBPath + "\*.*", TrueFSO.CreateFolder DBPathOn Error GoTo 0' Get the Palm database from the HotSync managerDim DBName As StringDBName = pdHSInfo.NameList(0)Dim pdQuery As New PDDatabaseQueryDim pdRecords As PDRecordAdapterSet pdRecords = pdQuery.OpenRecordDatabase(DBName, "PDDirect.PDRecordAdapter")' Open the handheld database, and iterate over the recordsDim Index As LongDim RecordId As VariantDim Category As LongDim Attributes As ERecordAttributesDim Data As VariantpdRecords.IterationIndex = 0Data = pdRecords.ReadNext(Index, RecordId, Category, Attributes)Do While Not pdRecords.EOF' In a HHtoPC sync, process all but deleted recordsIf Not CBool(CByte(Attributes) And CByte(eDelete)) ThenWriteRecContents DBPath, RecordId, DataEnd If' Read the next record and skip to the top of the loopData = pdRecords.ReadNext(Index, RecordId, Category, Attributes)Loop' Remove any deleted records, clear flags in Palm databasepdRecords.RemoveSet eRemoveAllDeletedRecordspdRecords.ResetAllModifiedFlagsLogSync pdSys, pdHSInfo.SyncTypeEnd 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 StringDBPath = pdHSInfo.PathName + "Ch4a"
The routine then deletes any files in that folder. Wrapping the delete operation in an error handler is necessary, because the FileSystemObject throws an error if the folder doesn't exist or if it is empty.
Dim FSO As New FileSystemObjectOn Error Resume NextFSO.DeleteFile DBPath + "\*.*", TrueFSO.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.
|
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 managerDim DBName As StringDBName = 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 PDRecordAdapterSet 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.
|
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 = 0Data = 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)) ThenWriteRecContents DBPath, RecordId, DataEnd
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 eRemoveAllDeletedRecordspdRecords.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 PDUtilityFilename = 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 .ckly, you should now have an appreciation of how to use the Sync Suite COM objects to access features and data on the Palm device.
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.
|
|
No record |
No change |
.CHG |
.NEW |
.DEL |
|---|---|---|---|---|---|
|
No record |
|
|
|
D |
|
|
No change |
|
|
D |
|
Remove DP |
|
Change |
|
P |
Conflict |
|
P |
|
New |
P |
|
|
|
|
|
Delete |
|
Remove DP |
D |
|
|
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)) ThenDeletedRec DBPath, strID, DataElseDirtyRec DBPath, strID, DataEnd 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
PDBDeleteRecordExto mark a record as both archived and deleted. You do this by passingafDeleteModeArchiveto 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 FileSystemObjectOn Error Resume NextFSO.DeleteFile DBPath + "\" + strID + ".REC", TrueOn Error GoTo 0End 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 VariantDim RecordId As VariantGetFileContents File, Data, pdUtil' Create a new Palm device recordRecordId = vbEmptypdRecords.Write RecordId, 0, 0, DataFile.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 IntegerDim sBuf As VariantDim RecordId As VariantDim bArray( ) As ByteFilenum = FreeFileOpen File.Path For Input As #FilenumInput #Filenum, sBufClose #Filenum' Convert to a byte arrayData = bArrayReDim Data(0 To Len(sBuf))pdUtil.BSTRToByteArray Data, 0, sBufEnd 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.
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.
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.
|
Related Reading Programming Visual Basic for the Palm OS |
Return to .NET DevCenter
Copyright © 2009 O'Reilly Media, Inc.