The Storage Kit: BResources

Derived from: (none)

Declared in: be/nustorage/Resources.h

Library: libbe.so


Overview

You may not want to be here... The BResources class was designed for a specific purpose: To provide a means to bundle application "resources" (icons, in particular) within the application executable itself. If you want to add new resources to your own application (resources that you want to have "stick" to the executable), then you've come to the right place. But you shouldn't use BResources to add data to a regular data file-- use attributes instead.

The data that a file contains is either "flat," or it's "structured." To read a flat file, you simply open it (through a BFile object) and start Read()'ing. Structured data requires that you understand the structure. Typically, an application understands the structure either because it's a well-known format, or because the application itself wrote the file in the first place.

The BResources class defines a simple design for storing structured data. The structure is a series of "resources," where each resource is key/value pair. A single "resource file" can hold an unlimited number of resources; a single resource within a resource file can contain an unlimited amount of data.

Resources are sort of like attributes in that they store chunks of data that are looked up through the use of a key. But note these differences:

Resources are stored in the file itself, such that if you copy the file, you copy the resources, as well.

Resources can't be queried.

Only plain files can have resources. (In other words, directories and symbolic links can't have resources.)


Initializing a BResources Object

The BResources class provides the means for reading and writing a file's resources, but it doesn't let you access the file directly. Instead, you must initialize theBResources object by passing it a valid BFile object, either in the constructor or the SetTo() function. Note the following:

The BFile that you pass in is copied by the BResources object. Thus, initializing a BResources object opens a new file descriptor into the file. You can delete the "original" BFile immediately after you use it to initialize the BResources object.

If the BFile that you pass in is open with write permission, the file is automatically locked by the BResources object (the object Lock()'s its copy of the BFile that you passed in). It's unlocked when you re-initialize or delete the BResources. Relatedly...

If you want to write resources, the BFile must not be locked when you pass it in. The BResources needs to be able to lock its copy of your object.

The BFile must be open for reading (at least).

Unfortunately, BResources lacks an InitCheck() function. If you want to check initialization errors, you should always initialize through SetTo(), rather than through the constructor.

Identifying and Creating Resource Files

You can't use just any old file as a BResources initializer: The file must be an actual resource file. Simply initializing a BResources object with an existing non-resource file will not transform the file into a resource file--unless you tell the initializer to clobber the existing file.

For example, this initialization fails:

   /* "fido" exists, but isn't a resource file. */
   BFile file("/boot/home/fido", B_READ_WRITE);
   BResources res;
   status_t err;
   
   if ((err = res.SetTo(&file)) != B_NO_ERROR) 
   ...

And this one succeeds...

   /* The second arg to SetTo() is the "clobber?" flag. */
   if ((err = res.SetTo(&file, true)) != B_NO_ERROR) 
   ...

...but at a price: fido's existing data is destroyed (truncated to 0 bytes), and a new "resource header" is written to the file. Having gained a resource header, fido can thereafter be used to initialize a BResources object.

Clobber-setting a resource file is possible, but...

As mentioned at the top of this document, you'll probably never create resource files directly yourself

So where do resource files come from if you don't create them yourself? Step right up...

Executables as Resource Files

The only files that are naturally resource-ful are application executables. For example, here we initialize a BResources object with the IconWorld executable:

   BPath path;
   BFile file;
   BResources res;
   
   find_directory(B_APPS_DIRECTORY, &path);
   path.Append("IconWorld");
   file.SetTo(&path, B_READ_ONLY);
   
   if (res.SetTo(&file) != B_NO_ERROR) 
      ...

The BResources object is now primed to look at IconWorld's resources. However...

An application's "natural" resources (its icons, signature, app flags) should be accessed through the BAppFileInfo class.


Resource Data

After you've initialized your BResources object, you use the FiddleResource() functions to examine and manipulate the file's resources:

Generative Functions

Data Functions

Info Functions

As mentioned earlier, the BFile that you use to initialize a BResources object must be open for reading. If you also want to modify the resources (by adding, removing, or writing) the BFile must also be open for writing.

Identifying a Resource within a Resource File

A single resource within a resource file is tagged with a data type, an ID, and a name:

Taken singly, none of these tags needs to be unique: Any number of resources (within the same file) can have the same data type, ID, or name.

It's the combination of the data type constant and the ID that uniquely identifies a resource within a file.

The name, on the other hand, is more of a convenience; it never needs to be unique when combined with the data type or with the ID.

Data Format

All resource data is assumed to be "raw": If you want to store a NULL-terminated string in a resource, for example, you have to write the NULL as part of the string data, or the application that reads the resource from the resource must apply the NULL itself. Put more generally, the data in a resource doesn't assume any particular structure or format, it's simply a vector of bytes.

Data Ownership

The resource-manipulating functions cause data to be read from or written to the resource file directly and immediately. In other words, the BResourceFile object doesn't create its own "resource cache" that acts as an intermediary between your application and the resource file. This has a couple of implications:

Resource data that you retrieve from or write to a BResourceFile object belongs to your application. For example, the data that's pointed to by the FindResource() function is allocated by the object for you--it's your responsibility to free the data when your finished with it. Similarly, the data that you pass to AddResource() (to be added as a resource in the file) must be freed by your application after the function returns.

The individual changes that you make to the resources are visible to other BResourceFiles (that are open on the same file) as soon as they are made. You can't, for example, bundle up a bunch of changes and then "commit" them all at the same time.


Reading and Writing a Resource File as a Plain File

Just because a file is a resource file, thatdoesn't mean that you're prevented from reading and writing it as a plain file (through the BFile object). For example, it's possible to create a resource file, add some resources to it, and then use a BFile object to seek to the end of the file and write some flat data. But you have to keep track of the "data map" yourself--if you go back and add more resources to the file (or extend the size of the existing ones), your flat data will be overwritten:

The BResources object doesn't preserve non-resource data that lives in the file that it's operating on.


Constructor and Destructor


BResources()


      BResources(void)
      BResources(BFile *file, bool clobber = false)

Creates a new BResources object. You can initialize the object by passing a pointer to a valid BFile; without the argument, the object won't refer to a file until SetTo() is called.

If clobber is true, the file that's referred to by BFile is truncated (it's data is erased), and a new resource file header is written to the file. If clobber is false and the file doesn't otherwise doesn't have a resource header, the initialization fails.

BResources copies the BFile argument; after the constructor returns, you can, for example, delete the BFile that you passed in.


~BResources()


      virtual ~BResources(void)

Destroys the BResources object.


Member Functions


AddResource()


      status_t AddResource(type_code type,
         int32 id,
         const void *data,
         size_t *length,
         const char *name = NULL)

Adds a new resource to the file. For this function to have an effect, the file must be open for writing. The arguments are:

Ownership of the data pointer isn't assigned to the BResourceFile object by this function; after AddResource() returns, your application can free or otherwise manipulate the buffer that data points to without affecting the data that was written to the file.

RETURN CODES

Bug: Currently, AddResource() will write over an existing resource. In this case, the function returns a positive integer (specifically, it returns the number of bytes that it just wrote), but it doesn't change the name of the resource. To work around this bug, you should call RemoveResource() just before calling AddResource().


FindResource()


      void *FindResource(type_code type,
         int32 id,
         size_t *length)

      void *FindResource(type_code type,
         const char *name,
         size_t *length)

Finds the resource identified by the first two arguments, and returns a pointer to a copy of the resource's data. The size of the data, in bytes, is returned by reference in *length.

It's the caller's responsibility to free the pointer that's returned by this function.

If the first two arguments don't identify an existing resource, NULL is returned.


GetResourceInfo()


      bool GetResourceInfo(int32 byIndex,
         type_code *typeFound,
         int32 *idFound,
         char **nameFound,
         size_t *lengthFound)

      bool GetResourceInfo(type_code byType,
         long andIndex,
         int32 *idFound,
         char **nameFound,
         size_t *lengthFound)

      bool GetResourceInfo(type_code byType,
         long andId,
         char **nameFound,
         size_t *lengthFound)

      bool GetResourceInfo(type_code byType,
         char *andName,
         int32 *idFound,
         size_t *lengthFound)

These functions return information about a specific resource, as identified by the first one or two arguments:

The other arguments return the other statistics about the resource (if found).

The pointer that's returned in *foundName belongs to the BResourceFile. Don't free it.

The functions return true if a resource was found, and false otherwise.


HasResource()


      bool HasResource(type_code type, int32 id)

      bool HasResource(const char *name, type_code type)

Returns true if the resource file contains a resource as identified by the arguments, otherwise it returns false.

Keep in mind that there may be more than one resource in the file with the same name and type combination. The type and id combo, on the other hand, is unique. See "Identifying a Resource within a Resource File."


ReadResource()


      status_t ReadResource(type_code type,
         int32 id,
         void *data,
         off_t offset,
         size_t length)

Reads data from an existing resource (identified by type and id) and copies it into the data buffer. offset gives the location (measured in bytes from the start of the resource data) from which the read commences, and length is the number of bytes you want to read. The data buffer must already be allocated and should be at least length bytes long.

You can ask for more data than the resource contains; in this case, the buffer is filled with as much resource data as exists (or from offset to the end of the resource). However, note that the function doesn't tell you how much data it actually read.

RETURN CODES


RemoveResource()


      status_t RemoveResource(type_code type, int32 id)

Removes the resource identified by the arguments. See "Identifying a Resource within a Resource File."

RETURN CODES


SetTo()


      status_t SetTo(BFile *file)

Unlocks and closes the object's previous BFile, and re-initializes it to refer to a copy of the argument. If the new BFile is open for writing, the BResources' copy of the BFile is locked.

RETURN CODES


WriteResource()


      status_t WriteResource(type_code type,
         int32 id,
         void *data,
         off_t offset,
         size_t length)

Writes data into an existing resource, possibly overwriting the data that the resource currently contains.

If the new data is placed such that it exceeds the size of the current resource data, the resource grows to accommodate the new data.

You can't use this function to "shrink" a resource. To remove a portion of data from a resource, you have to remove the resource and then re-add it.

RETURN CODES






The Be Book, in lovely HTML, for the BeOS Preview Release.

Copyright © 1997 Be, Inc. All rights reserved.

Be is a registered trademark; BeOS, BeBox, BeWare, GeekPort, the Be logo, and the BeOS logo are trademarks of Be, Inc.

Last modified July 18, 1997.