Home > Programmer's Guide > More About Programming > Adding Program Functionality
Expansion Cards
Palm OS® integrates a mechanism called Virtual File System Manager or VFS Manager. This mechanism, present on most of the recent device, allows the programmer to access in a transparent manner different types of file systems such as VFAT, HPFS and NFS, on many different types of media, including Multi Media card, Secure Digital card, Compact Flash, Memory Stick, and SmartMedia.
HB++ implements this mechansim through the VFSVolume and StreamFile classes. The VFSVolume class implements the functionality to manage expansion cards available on certain handheld device models with Palm OS® 4.0 or higher. The StreamFile class allows manipulation of files stored on external media. The functionality offered by this object is only available on Palm OS® 4.0 and higher, and on handhelds devices equiped with an extension slot (see also Data Streams).
We are going to refer to the VFSDemo sample to illustrate the different functions of these objects.
File management functionality
First of all, we are going to study the class VFSVolume that manages expansion cards. The presence of one or more expansion cards on a handheld device is not automatic. It is worth checking that they are present and to list them. The VFSVolume class offers this functionality using the FindFirstVolume and FindNextVolume methods. The FindFirstVolume method finds the first available volume. If this method returns False, this means that there is no available card on the handheld device.
If it returns True, this means that at least one volume has been found and that the properties Reference, Label, FileSystem and MediaType of the VFSVolume object have been initialized. In the VFSDemo sample, we chose to populate a popup object with all the volumes present on the handheld device which run the application. We are going to write a procedure called RefreshVolumeList that will populate the popup object:
'------- Local variables declaration
dim volumes (0 to 15) as VFSVolume
dim currentVolume as VFSVolume
dim szPath as String
dim szParentPath as String
dim bIsEmpty as boolean
private sub RefreshVolumeList()
'fill the volume list
dim i as integer
dim continue as Boolean
dim volume as new VFSVolume
dim szText as string
lstVolumes.clear
i = 0
'loop while a new volume is found
continue = volume.FindFirstVolume()
while (continue)
'store each volume found in an array
Set volumes(i) = clone(volume)
szText = volume.Label
szText = szText & "(" & GetType(volume) & ")"
'add an item in the list box
lstVolumes.addItem(szText),i
i=i+1
continue=volume.FindNextVolume()
wend
if i = 0 then
msgbox "No volume was mounted !"
app.Quit
end if
End SubYou will notice that each volume found is cloned in an array called volumes(). The index of each of these volumes in the array corresponds to their index in the lstVolumes object. The Volumes() array is declared locally as an array of type VFSVolume. The Clone statement instantiates an object of type VFSVolume, copies the volume object into this instance and stores the reference in Volumes(i), this being the index corresponding to the position in the lstVolumes object. Thus, whichever volume is chosen by the user in the lstVolumes object, we will have direct access to this volume in the Volumes() array.
The following function allows us to use the MediaType property of the VFSVolume class in order to return a string containing the volume type. This function is used in the RefreshVolumeList procedure:
private function GetType(byref Vol as VFSVolume) as String
select case Vol.MediaType
case hbMediaCompactFlash
GetType = "Compact Flash"
case hbMediaEmulator
GetType = "Emulator"
case hbMediaMemoryStick
GetType = "Memory Stick"
case hbMediaMultiMediaCard
GetType = "MMC"
case hbMediaRAMDisk
GetType = "RamDisk"
case hbMediaSecureDigital
GetType = "SD"
case hbMediaSmartMedia
GetType = "SmartMedia"
case else
GetType = "Unknown"
end select
End FunctionIn order to clearly distinguish each volume in the lstVolumes object, we will use the Label and MediaType properties. The Label property returns the volume name and the MediaType property identifies the type of card inserted.
The VFSVolume class also retrieves other information about the card. We will display this information when the Click event on the mnuMediaInfo menu is fired in the frmVFSDemo form. The code for this procedure is as follows:
Private Sub mnuMediaInfo_Click()
dim sz as String
if not currentVolume is Nothing then
sz="Name : " & currentVolume.Label & "\n"
sz=sz & "Type : " & GetType(currentVolume) & "\n"
sz=sz & "Capacity : " & currentVolume.TotalBytes /1024 & " Kb \n"
sz=sz & "Free space : " & currentVolume.FreeBytes /1024 & " Kb \n"
sz=sz & "Used space : " & (currentVolume.TotalBytes-currentVolume.FreeBytes) /1024 & " Kb \n"
sz=sz & "Pdb,Prc folder: " & currentVolume.DefaultDirectory(".pdb") & "\n"
sz=sz & "Jpeg folder: " & currentVolume.DefaultDirectory(".jpeg") & "\n"
sz=sz & "Mp3 folder: " & currentVolume.DefaultDirectory(".mp3") & "\n"
MsgBox sz,hbMsgBoxInformation
Else
MsgBox "You must select a volume first.",hbMsgBoxInformation
lstVolumes.HitControl
End if
End SubThe total size of the card in bytes is determined by the TotalBytes property. The free space is obtained by the FreeBytes. The DefaultDirectory method determines which directories are associated with which files, these directories depend on the type of card and its associated driver.
The VFSVolume object carries out common actions like listing files and directories. We will see how to implement this functionality in the VFSDemo application through a procedure called RefreshFileList. The procedure will populate the lstFiles list with all the entries from the current directory, stored as a local variable in the format szPath and distinguish directories from files using the FindFirstFile and FindNextFile methods:
'list all the files of the current directory stored in szPath
Private Sub RefreshFileList()
dim continue as Boolean
dim szName as String
dim eAttribs as HbFileAttribute
'tip : when you add an item to a list, it will be redrawn.
'setting the redraw property to false when adding numerous items simultaneouslty
'is a good way of increasing the speed of your program
lstFiles.redraw = false
lstFiles.clear
'We will fill lstFiles itemData property with
' 0 if the current file is a regular file
' 1 if the current file is a directory
' 2 if the list item is the pseudo directory .., meaning way to go to the
' parent directory
szParentPath = Parent(szPath)
if (szParentPath <> szPath) then
lstFiles.addItem(".."),2
end if
fldSelection.text ="Refreshing..."
'Get the first directory file
continue = currentVolume.FindFirstFile(szPath & "/", szName, eAttribs)
bIsEmpty = not continue
while (continue)
if (eAttribs and hbFileAttrDirectory)<>0 then
'the current file is a directory
lstFiles.addItem("/"&szName),1
else
'the current file is a not a directory, it's a file
lstFiles.addItem(szName),0
end if
'Get the next file
continue=currentVolume.FindNextFile(szName, eAttribs)
wend
fldSelection.text = szPath
loadActionList(1)
lstFiles.redraw = true
End SubThe VFSVolume object also creates directories on the card using the CreateDirectory method. In the VFSDemo application, you will find the following procedure which creates a directory (the szPath variable is a local variable in the form and contains the current path):
'Create a new directory
private sub CreateFolder()
dim frmIn as new frmInput
'Get user input for new directory name
frmIn.szTitle= "New directory name"
frmIn.show hbFormModal + hbFormPopup
'If user do not cancel the input
if not frmIn.bCancelled then
'Create the new directory
currentVolume.CreateDirectory szPath & "/" & frmIn.szInput
'Refresh the file list
RefreshFileList
End if
End SubThe VFSVolume object also allows renaming files or directories on the card using the Rename method. In the VFSDemo application, you will find the following procedure which renames a file or directory. In our example, the fldSelection.text property contains either the complete filepath or the path of the directory to rename. If the lsDirectory parameter is True, this indicates that the name change will affect a directory.
The szPath variable is a local variable in the form and contains the current filepath. If we are renaming a directory, it is useful to update this variable before refreshing the file list in order to avoid an error.
'Rename a file or a directory
private sub RenameFile(byval IsDirectory as Boolean)
dim frmIn as new frmInput
'Get user input for new file or directory name
frmIn.szTitle= "New name for " & fldSelection.text
frmIn.show hbFormModal + hbFormPopup
'If user do not cancel the input
if not frmIn.bCancelled then
'Rename the file or the directory
currentVolume.rename fldSelection.text, frmIn.szInput
if IsDirectory then szPath=szParentPath & "/" & frmIn.szInput
'Refresh the file list
RefreshFileList
End if
End SubThe VFSVolume object also allows deleting files or directories on the card using the Delete method. In the VFSDemo application, you will find the following procedure which deletes a file or directory. In our example, the fldSelection.text property contains either the complete filepath or the path of the directory to rename. If the lsDirectory parameter is True, this indicates that the deletion will affect a directory.
The szPath variable is a local variable in the form and contains the current filepath. If we are deleting a directory, it is useful to update this variable before refreshing the file list in order to avoid an error. In effect,
'Delete a file or a directory
private sub DeleteFile(byval IsDirectory as Boolean)
dim eAns as integer
'Get user confirmation
eAns = msgBox("Remove " & fldSelection.text & " ?",hbMsgBoxConfirmation+hbMsgBoxYesNo)
if eAns = hbMsgBoxYes then
'if user confirm, delete the file
currentVolume.Delete fldSelection.text
if IsDirectory then szPath=szParentPath
'Refresh the file list
RefreshFileList
End if
End SubNote: Trying to delete a file which has the hbFileAttrReadOnly attribute will cause an error. In the same way, trying to delete a directory that is not empty will also cause an error and you should therefore delete all entries in a directory before deleting the directory itself.
Accessing a file
Having the capability to manipulate a file, you should obtain a volume reference using the methods of the VFSVolume object. You can then open a file using its name by calling the Open method, manipulate it using the methods inherited by the Stream class, such as Read and Write. Finally, when you have finished using a file, you can close it using the Close method. File management with HB++ is not greatly different from file management on a desktop computer with other programming languages.
The Created, Accessed and Modified properties allow access respectively to creation dates, last access and last modification date of a file. The Attributes property shows where to modify the file attributes. In the VFSDemo, you will find the following procedure that displays and modifies the attributes of the file selected by the user.
private sub ShowFileProperties()
dim f as new frmProperties
dim sf as new StreamFile
dim attributes as HbFileAttribute
'open file
sf.Open currentVolume.Reference,fldSelection.text,hbModeOpenExisting+hbModeReadWrite
'Get file properties and attributes
f.szName=filename(fldSelection.text)
f.dCreated=sf.Created
f.dModified=sf.Modified
f.dAccessed=sf.Accessed
f.bReadOnly=(sf.Attributes and hbFileAttrReadOnly)<>0
f.bHidden=(sf.Attributes and hbFileAttrHidden)<>0
f.bSystem=(sf.Attributes and hbFileAttrSystem)<>0
f.bArchive=(sf.Attributes and hbFileAttrArchive)<>0
'Store File attributes
attributes =sf.Attributes
f.Show hbFormModal+hbFormPopup
'Reset file attributes
attributes=attributes and not hbFileAttrReadOnly
attributes=attributes and not hbFileAttrHidden
attributes=attributes and not hbFileAttrSystem
attributes=attributes and not hbFileAttrArchive
'update file attributes
if f.bReadOnly then
attributes=attributes or hbFileAttrReadOnly
end if
if f.bHidden then
attributes=attributes or hbFileAttrHidden
end if
if f.bSystem then
attributes=attributes or hbFileAttrSystem
end if
if f.bArchive then
attributes=attributes or hbFileAttrArchive
end if
sf.Attributes=attributes
'Close file
sf.Close
End SubIn our example, the fldSelection.text contains the complete path. The FileName function allows us to retrieve the file name from its full path. This function is included in the sample. There are other file attributes that are not taken into account in this procedure such as hbFileAttrDirectory, hbFileAttrLink and hbFileAttrVolumeLabel.
Although it does not cause an error, it is strongly recommended that you do not use this property to modify these attributes beacause they are used by the file management system and modifying them may make the card unreadable.
The StreamFile object gives access to the file's contents. In the VFSDemo application, you will find the following procedure that displays the contents of a file. This procedure shows how to use the Read, Write and other methods inherited from the Stream object.
Private Sub ShowFileData(byval HexEdit as Boolean)
dim streamFileOpened as new StreamFile
dim iSize as integer
dim s as String, bByte as byte
dim fv as new frmView
'open the selected file
streamFileOpened.open currentVolume.Reference, fldSelection.text,hbModeOpenExisting +hbModeReadOnly
'If file size is > 4 kb ....
if (streamFileOpened.Size() > 4096) then
'set the size to read to 4 kb
msgbox "File will be truncated to its first 4 kb !"
iSize = 4096
Else
'Else get the real file size
iSize = streamFileOpened.Size()
End if
'put the file data (4 kb or less) in a string
if HexEdit then
'Hexadecimal view
While streamFileOpened.position<iSize
streamFileOpened.Read bByte
s = s & hex(bByte) & " "
Wend
Else
'Text view
read streamFileOpened, s[iSize]
End if
'Close the StreamFile
streamFileOpened.Close
'Show the file data
fv.caption = filename(fldSelection.text)
fv.fldDisp.text = s
fv.show hbFormPopup+hbFormModal
End SubIn our example, the fldSelection.text property contains the complete file path. If the size of the file returned by the Size property of the StreamFile class is greater than 4kb, we will only show the first 4kb of the file. Given that the visualisation is carried out in a Field type object, a binary file will not give a good result. If the HexEdit parameter is True when this procedure is called, reading will take place in hexadecimal mode.
It is possible to use the StreamFile and the DatabaseInfo classes to copy databases from the handheld device memory to a memory card and vice versa. In the VFSDemo example, we implement Backup and Restore functionality. For this, we use two generic functions. The first, CopyDatabaseToVFS, copies a database from the handheld device memory onto a memory card.
Public Function CopyDatabaseToVFS(ByVal iVolRef as Integer, ByRef db as DatabaseInfo, ByRef sName as String) as Integer Dim sf as New StreamFile On Error Goto ERR 'Create a file named as Database name sf.Open iVolRef, sName, hbModeCreateAlways+hbModeReadWrite 'Write the whole database in the streamFile sf.Write db 'Close the StreamFile sf.Close CopyDatabaseToVFS=0 Exit Function ERR: CopyDatabaseToVFS=err.Number End Function
The second, RestoreDatabaseFromVFS, copies a file from the memory card into the handheld device memory.
Public function RestoreDatabaseFromVFS(ByVal iVolRef as Integer, ByRef sName as String) as Integer Dim di as new DatabaseInfo Dim fd as new StreamFile On error goto ErrRestore 'Open the file fd.Open iVolRef,sName,hbModeOpenExisting+hbModeReadOnly 'Read it in a databaseInfo : Doing this create a database in PDA memory fd.Read di 'Close the file fd.Close exit function ErrRestore: RestoreDatabaseFromVFS=Err.Number End Function
In the VFSDemo application, we created two menu items with the following code:
The first, in the Click event of the mnuBackupHere menu item saves the databases in the device memory.
Private Sub mnuBackupHere_Click()
dim db as new DatabaseInfo
'Enumerate all database in memory
If db.FindFirstByTypeCreator("","") Then
Do
'copy the current database in the current directory
CopyDatabaseToVFS currentVolume.Reference ,db,szPath & "/" & db.Name
'Inform user
fldSelection.Text="Backup " & db.Name
'Proceed next database
Loop While db.FindNext
End If
'Actualize the file list
RefreshFileList
End SubThe second, in the Click event of the mnuRestoreFromHere menu item restores files from the current directory into the PDA's memory.
Private Sub mnuRestoreFromHere_Click()
dim continue as Boolean
dim szName as String
dim eAttribs as HbFileAttribute
'Enumerate all file in the current directory
continue = currentVolume.FindFirstFile(szPath & "/", szName, eAttribs)
bIsEmpty = not continue
while (continue)
'if this is not a directory
if (eAttribs and hbFileAttrDirectory)=0 then
'restore the database
RestoreDatabaseFromVFS currentVolume.Reference,szPath & "/" & szName
'Inform user
fldSelection.Text="Restore " & szName
end if
'Proceed next file
continue=currentVolume.FindNextFile(szName, eAttribs)
wend
fldSelection.text = szPath
End SubGiven that the backup procedure saves all the databases, including those in ROM memory, it will not be possible to copy them from the restore procedure. It is therefore important to provide an error handler to manage this scenario.
Using external C calls
The Reference property of the VFSVolume class returns a reference number of the volume useable with the Palm OS® Virtual File System Manager functions. The FileRef property of the StreamFile class returns a file handle useable with the Palm OS® Virtual File System Manager functions. These properties allow you to access a volume or a file opened with HB++ from a shared library written in C.
In the VFSDemo application, we implemented Palm OS® API calls in the Click event of the mnuVFSAPICall as is shown in the mnuVFSAPICall_Click().
Note: for further details about API declarations and shared libraries, refer to the following chapters: Declare statement, LoadLibrary and RemoveLibrary.
'Module ExternalAPI.hbm : VFS API Declarations
Public Declare Function VFSVolumeGetLabel(byval volRefNum as Integer,byref szlabel as String, byval bufLen as Integer) as Integer Trap &HA348, 29
public Declare Function VFSVolumeSize(byval volRefNum as Integer,byref volumeUsedP as long, byref volumeTotalP as long) as Integer Trap &HA348, 31
Public Declare Function VFSFileSize(ByVal iRef as long, ByRef lSize as long) as Integer Trap &HA348, 18
Private Sub mnuVFSAPICall_Click()
dim e as Integer,lSize as long
dim szVolume as string,iSize as Integer
dim lUsed as long,lTotal as long
Dim fs as new StreamFile
on error goto ErrGest
fs.Open currentVolume.Reference,fldSelection.Text,hbModeOpenExisting+hbModeReadOnly
e=VFSFileSize(fs.FileRef,lSize)
If e=0 then
MsgBox fldSelection.Text & " size : " & lSize/1024 & " kb" ,hbMsgBoxInformation,"VFSFileSize API call"
End if
fs.Close
e=VFSVolumeSize(currentVolume.Reference,lUsed,lTotal)
If e=0 then
Msgbox lUsed/1024 & "kb used / " & lTotal/1024,hbMsgBoxInformation,"VFSVolumeSize API call"
End if
szVolume=space(256)
iSize=len(szVolume)
e=VFSVolumeGetLabel(currentVolume.Reference,szVolume,iSize)
If e=0 then
Msgbox "Volume name is : " & szVolume,hbMsgBoxInformation,"VFSVolumeGetLabel API call"
End if
Exit Sub
ErrGest:
Msgbox "You must select a file first!"
End SubIn this procedure, the fldSelection.text should contain the complete file path. If this is the case, an instance of VFSVolume is initialized and the procedure will be carried out successfully. Note: running this sample on the Palm OS® emulator can give erroneous results. This is because the HostFS.prc program does not simulate the presence of extension cards properly. It is therefore preferable to check this function on a handheld device for the best results.
You may wish to read the documentation on VFSVolume and StreamFile classes ot find out further details on their different properties and methods.