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.
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 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
FileSystemObject
throws 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 |
---|---|
|
Makes an entry in either the desktop or device HotSync log. |
|
Creates a data type database. Records are unstructured. |
|
Creates a resource-type database. Each record has a structure, such as an icon or form. |
|
Retrieves maximum supported record size on the device. |
|
Opens an existing data type database. |
|
Opens an existing resource-type database. |
|
Retrieves number of databases in device RAM. |
|
Retrieves statistics and settings for a database. |
|
Retrieves statistics and settings for a database. |
|
Retrieves list of databases in RAM or ROM. |
|
Deletes a RAM or ROM database. |
|
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 |
---|---|
|
Retrieves mode(s) used to open database |
|
Makes an entry in either the desktop or device HotSync log |
|
Changes the Category ID for a group of records |
|
Sets modification date/time on database prior to close |
|
Retrieves the database name |
|
Indicates end-of-file when using an iterator |
|
Sets the maximum size for read/write buffers |
|
Sets the start offset in the database for an iterator |
|
Represents category data for this database |
|
Represents database for this record adapter |
|
Reads application-specific data, including categories |
|
Gets record information by index or identifier |
|
Gets record information from an iterator |
|
Retrieves application-specific data, notionally used for sorting |
|
Retrieves list of record identifiers in database |
|
Retrieves count of records in database |
|
Permanently erases a record from database |
|
Permanently erases a group of records from database |
|
Clears the dirty bit for all records in database |
|
Creates or updates a database record and attributes |
|
Writes application-specific data, including categories |
|
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.
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.
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.
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.