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 Sub

You 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 Function

In 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 Sub

The 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 Sub

The 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 Sub

The 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 Sub

The 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 Sub

Note: 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 Sub

In 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 Sub

In 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 Sub

The 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 Sub

Given 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 Sub

In 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.