http://www.developer.com/

Back to article

How Palm OS Expands Your Applications: Files and Folders


June 8, 2005

In my previous article, "How Palm OS Expands Your Applications: Volumes," you learned how to manipulate volumes using VFS Manager. Now, it is time to move on and study Folders and Files. For those of you who are familiar with Windows or Unix programming, these topics will acquaint you with the same philosophy. The next sections will guide you through various operations you can handle over Folders and Files using Virtual File System Manager.

Open, Close, and Enumerate Folders and Files

Prior to making any step forward, I should discuss how VFS Manager addresses folders anf files on some mounted volume. Similar to Windows CE, VFS Manager has no concept of a "current directory;" hence, all paths are absolute within a given volume. You can't use relative paths. The folder separator is a slash character ("/"), and volume's root is addressed as "/".

VFS Manager supports all standard operations on folders and files. Like in many other systems, you may enumerate, create, detete, and rename folders or files, and get or set their attributes and times. Actually, files and folders are handled in a very similar way.

Your starting point will be a folder contents enumeration sample. To proceed with your folders or files, you first need to open them. VFSFileOpen's definition looks pretty simple:

Err VFSFileOpen (UInt16 volRefNum, const Char *pathNameP,
                 UInt16 openMode, FileRef *fileRefP)

You're providing a path and desired mode and getting back a reference to open file/folder. This reference will be used later with all possible operations your applicaiton will carry out upon this file/folder. You can control how the file or folder will be opened via the openMode parameter, which allows you to create files in case they don't exist.

After you've opened some folder, an enumeration is a simple thing. Just take a look at the following sample to admit it:

FileInfoType FileInfo;
FileRef rDir;
UInt32 nDirIterator = vfsIteratorStart;
Char szFileName[256];

// volRefNum is obtained earlier
Err err = VFSFileOpen(volRefNum, "/", vfsModeRead, &rDir);
if(err == errNone)
{
   FileInfo.nameP = szFilename;
   FileInfo.nameBufLen = sizeof(szFilename);
   while (nDirIterator != vfsIteratorStop)
   {
      err = VFSDirEntryEnumerate(rDir, &nDirIterator, &FileInfo);
      if (err == errNone)
      {
         HostTraceOutputTL(appErrorClass,"Found <%s>",szFileName);
      }
      else
      {
         // handle an error
      }
   }
}
else
{
   // handle directory open error here
}

As you see, all the process is not recursive. You sill need to open a folder before you can enumerate it. At this time, please note that you get the detected file's attributes in the FileInfo.attributes member, so you'll easily be able to determine that this particular file is actually a folder and proceed with it as needed.

When you no longer need a particular file or folder resource, you should close it by calling the following:

Err VFSFileClose (FileRef fileRef)

Common Operations

VFS Manager supports a standard set of common file operations. They are listed below, grouped by operation:

Err VFSDirCreate(UInt16 volRefNum,  const Char *dirNameP)
Err VFSFileCreate(UInt16 volRefNum, const Char *pathNameP)
Err VFSFileDelete(UInt16 volRefNum, const Char *pathNameP)
Err VFSFileRename(UInt16 volRefNum, const Char *pathNameP,
                  const Char *newNameP)

Err VFSFileSize(FileRef fileRef, UInt32 *fileSizeP)
Err VFSFileResize(FileRef fileRef, UInt32 newSize)

Err VFSFileGetAttributes(FileRef fileRef, UInt32 *attributesP)
Err VFSFileSetAttributes(FileRef fileRef, UInt32 attributes)

Err VFSFileGetDate(FileRef fileRef, UInt16 whichDate, UInt32 *dateP)
Err VFSFileSetDate(FileRef fileRef, UInt16 whichDate, UInt32 date)

Err VFSGetDefaultDirectory(UInt16 volRefNum,
                           const Char *fileTypeStr,
                           Char *pathStr, UInt16 *bufLenP)
Err VFSRegisterDefaultDirectory(const Char *fileTypeStr,
                                UInt32 mediaType,
                                const Char *pathStr)
Err VFSUnregisterDefaultDirectory(const Char *fileTypeStr,
                                  UInt32 mediaType)

I won't discuss them all in detail, but rather highlight some valuable points. First off, VFSDirCreate and VFSFileCreate don't open a folder or file. You still should open it manually in case you want to perform any operations on this file or folder.

The second thing I'd like to mention is a notion of a "default directory." I have already talked about it in my previous article, but let's repeat it here as well. Palm OS 4.x and higher has a mechanism that allows mapping file extensions or MIME types to specific folder names. Such a mechanism gives you a convenient way to determine where data files are (or should be) stored for a given expansion slot. The last three functions from the list above handle all possible situations you might have. You probably will want to define your own default directory to keep data that is specific to your application. Thus, you have a clue about how to do it.

Exploring File I/O Stuff

Well, I guess you won't be satisfied only by manipulating folders or files. A real application usually needs to perform some I/O operations, loading and saving its data. Here, you have a number of options.

If you keep your data in some format other that PDB or PRC, you can use VFSFileXXX functions to get direct access to such data. Usually, expansion cards contain a large amount of information, so this is the preferable way to manage expansion-card located data. You can't use Data Manager to manipulate native Palm databases stored on expansion cards.

The next sample code demonstrates how to sequentially save to the file some messages located on an expansion slot:

FileRef rFile;

// volRefNum is obtained earlier
Err err = VFSFileOpen(volRefNum, "/PALM/test.log",
                      vfsModeCreate|vfsModeWrite, &rFile);
if(err == errNone)
{
   err = VFSFileSeek(rFile,vfsOriginEnd,0);
   Char szLog[255];
   UInt32 nNumBytesWritten = 0;
   for (int i = 0; i < 100; i++)
   {
      StrPrintF(szLog,"Line #%d\r\n",i);
      err = VFSFileWrite(rFile, StrLen(szLog), (const void *)szLog,
                         nNumBytesWritten);
   }
   VFSFileClose(rFile);
}

This code is pretty similar to regular I/O programming under Windows or Unix. All this code does is open the file, set current file pointer to the end, and append the next line. It's as easy as possible.

Nevertheless, you may get read-only access to data and resources saved on expansion cards. The following funcitions are all about this kind of stuff:

Err VFSFileDBGetResource(FileRef ref, DmResType type, DmResID resID,
                         MemHandle *resHP)
Err VFSFileDBInfo(FileRef ref, Char *nameP,
                  UInt16 *attributesP, UInt16 *versionP,
                  UInt32 *crDateP, UInt32 *modDateP,
                  UInt32 *bckUpDateP, UInt32 *modNumP,
                  MemHandle *appInfoHP, MemHandle *sortInfoHP,
                  UInt32 *typeP, UInt32 *creatorP,
                  UInt16 *numRecordsP)
Err VFSFileDBGetRecord(FileRef ref, UInt16 recIndex,
                       MemHandle *recHP,
                       UInt8 *recAttrP, UInt32 *uniqueIDP)

Their usage and syntax are equivalent to DmGetRecord and so forth functions; the only difference is that they work with data stored on an expansion card.

The next option to be considered is to place your data files under the "/PALM/Launcher" folder so they can be automatically loaded into RAM from an expansion card, together with your application. Such databases, called "bundled databases," require a dmHdrAttrBundle flag to be set. They are removed from main memory at the moment user exits your application or switches to another one. Bundled databases are intended for read-only access because they aren't copied back to the card. From the other side, all databases created by your applicaition in main memoty will remain there after the application itself exits. At the next run, you can use such data without any problem.

And, finally, you have the way to manually transfer native Palm databases between an expansion card and main memory. Moreover, Palm OS provides you with the means to display the progress of the operation while doing so.

Err VFSImportDatabaseFromFile(UInt16 volRefNum,
                              const Char *pathNameP,
                              UInt16 *cardNoP, LocalID *dbIDP)

Err VFSImportDatabaseFromFileCustom(UInt16 volRefNum,
                                    const Char *pathNameP,
                                    UInt16 *cardNoP, LocalID *dbIDP,
                                    VFSImportProcPtr importProcP,
                                    void *userDataP)

Err VFSExportDatabaseToFile(UInt16 volRefNum, const Char *pathNameP,
                            UInt16 cardNo, LocalID dbID)

Err VFSExportDatabaseToFileCustom(UInt16 volRefNum,
                                  const Char *pathNameP,
                                  UInt16 cardNo, LocalID dbID,
                                  VFSExportProcPtr exportProcP,
                                  void *userDataP)

These two pairs of functions do opposite moves: The first ones copy data from the expansion card into main memory while the second pair converts database from its internal Palm OS format into equivalent PDB or PRC and transfers it to expansion card. 'Custom' functions takes callback as one of the parameters, so you can inform the user about operation progress. All these functions use Exchange Manager technique to copy data from source to destination. It means that, for example, VFSExportDatabaseToFileCustom internally calls ExgWriteDB and provides its own callback to copy data. I'll put here a shortened excerpt from the attached code sample to illustrate the exporting custom process:

struct UserData
{
   const char *szFileName;
   ProgressType* pProgressBar;
};

static Boolean CallbackFunc(PrgCallbackDataPtr cbP)
{
   const char* szName = ((UserData*)(cbP->userDataP))->szFileName;
   if ( cbP->stage == 0 )
   {
      StrPrintF(cbP->textP,"Exporting Database %s",szName);
   }
   else
   {
      StrPrintF(cbP->textP,"Exporting Database %s: %d%%",szName,
                cbP->stage);
   }
   cbP->textChanged = true;

   return true;
}


static Err VFSExportCustomProc(UInt32 totalBytes, UInt32 offset,
                               void *userDataP)
{
   UserData* pData = (UserData*)userDataP;
   if ( offset == 0 )
   {
      pData->pProgressBar =
         PrgStartDialog("Exporting Database",CallbackFunc,pData);

      if ( pData->pProgressBar )
         return errNone;
      else
         return memErrNotEnoughSpace;
   }

   PrgUpdateDialog(pData->pProgressBar,0,offset*100/totalBytes,
                   "",true);

   EventType event;
   Boolean handled;
   do
   {
      EvtGetEvent(&event,0);
      handled = PrgHandleEvent(pData->pProgressBar,&event);
      if ( !handled )
      {
         if ( PrgUserCancel(pData->pProgressBar) )
            return exgErrUserCancel;
      }
   }
   while (event.eType != sysEventNilEvent);

   return errNone;
}

Boolean CMainForm::OnExportCustom(EventPtr pEvent,
                                  Boolean& bHandled)
{
   DoDataExchange(DDX_ControlToData);

   Err err;
   LocalID dbID = 0;
   CString sLine;
   CDatabase DB;
   err = DB.OpenOrCreate(0,(const char*)m_sPDBName,'ALEX','data');
   for (int i = 0; i < 1000; i++)
   {
      sLine.Format("Line #%d",i);
      CRecordStream rs(&DB);
      rs << sLine;
      rs.Close();
   }

   DB.GetDbUniqueID(dbID);
   DB.Close();

   UInt16 volRefNum = -1;
   UInt32 volIterator = vfsIteratorStart;

   CString sInfo;
   while ( volIterator != vfsIteratorStop )
   {
      err = VFSVolumeEnumerate(&volRefNum, &volIterator);
      if (err == errNone)
      {
         VolumeInfoType volInfo;
         err = VFSVolumeInfo(volRefNum,&volInfo);
         sInfo.Format("Attr: %lu, Type: %s, Class = %s,
                      MediaType = %s", volInfo.attributes,
                      (const char*)GetFileSystemType(volInfo.fsType),
                      (const char*)GetMountClass(volInfo.mountClass),
                      (const char*)GetMediaType(volInfo.mediaType));
                      DDX_Text(&m_fldDescription, sInfo,
                      DDX_DataToControl);
         break;
      }
      else
      {
         // handle the error
      }
   }

   char szFileName[256];
   StrPrintF(szFileName,"/%s",(const char*)m_sFileName);
   UserData userData;
   userData.szFileName = szFileName;
   userData.pProgressBar = NULL;
   err = VFSExportDatabaseToFileCustom(volRefNum,(const char*)
                                       szFileName,0,dbID,
                                       VFSExportCustomProc,
                                       &userData);
   if ( userData.pProgressBar )
      PrgStopDialog(userData.pProgressBar,(err == exgErrUserCancel));

   return true;
}

This sample application creates a PDB into main memory with 1000 text lines and then exports it to the very first available mounted volume. The code snippet shows various aspects of VFS Manager programming: volume enumeration, getting info, displaying progress bar, and so forth. Similar code will import data from the expansion card to main memory. The sample uses a POL library to make life easier, but I guess this won't disturb you in catching the main idea.

Conclusion

On this optimistic note, you will have a break with VFS Manager. As you can see, all the stuff is relatively simple and works really well. From the other side, it gives you a powerful mechanism to manage a large amount of data without making too big an effort. I hope this series will help you understand all black niches of the VFS Manager API. In the next article, you will continue to investigate this area and talk about Expansion Manager capabilities.

Download the Source Code

Download the accompanying code's zip file here

About the Author

Alex Gusev started to play with mainframes in the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date