Copyright ©1995 by NeXT Computer, Inc.  All Rights Reserved.

IXNameAndFileAccess



Adopted By: IXBTree
IXFileFinder
IXRecordManager
IXStoreDirectory
Incorporates: IXBlockAndStoreAccess
Declared In: store/protocols.h



Protocol Description

The IXNameAndFileAccess protocol defines methods for initializing and freeing store file clients by name instead of by block handle.  A store file client is an object that uses data in a store file (see below).  You use this protocol to create new store file clients, to initialize clients from data previously created in a store file, and to destroy store file clients.  For general information on store clients, see the IXBlockAndStoreAccess protocol specification.



Store Files

Files with a ".store" extension are assumed by convention to contain an IXStoreDirectory at block handle 1.  You can take advantage of this convention when implementing IXNameAndFileAccess for your class by accessing the contents of the store file through its IXStoreDirectory.

Note:  Although the term "store file" can refer to any file created by an IXStoreFile, the term "file-based store" is preferred. "Store file," in this protocol specification, refers specifically to a file-based store obeying the above convention;  that is, a storage file having an IXStoreDirectory at block handle 1 (the ".store" extension merely advertises this fact, and is entirely optional).

A store file can be created by allocating an IXStoreDirectory and sending it initInStore:, passing a newly created IXStoreFile (with the filename ending in ".store" if desired).  This method will allocate a block handle for the IXStoreDirectory.  An IXStore guarantees that the first block allocated from it will have a handle of 1, so you can safely use this technique to create a store file. Note, however, that if allocations occur before the store directory is initialized, this guarantee no longer holds.  Therefore, if allocations may occur before the store directory is initialized, your code should verify that the handle allocated for the store directory is in fact 1.

To open a store file, initialize an IXStoreFile object with the initFromFile:forWriting: method, and then allocate an IXStoreDirectory and send it an initFromBlock:inStore: message with 1 as the block handle and the id of the IXStoreFile as the store.  You can then use the IXStoreDirectory to access entries in the store file by name, either reconstituting entries directly with openEntryNamed:, or getting the boot block and class for an entry and sending initFromBlock:inStore: messages to newly allocated instances of those store file client classes.

The IXStoreDirectory at block 1 is intended as a public root directory for implementors of this protocol.  Your store file clients shouldn't use it to store named private clients.  Any clients belonging to a store file client should be accessed only through that client's boot block, and referenced by handle.  If your client needs to store other (private) clients by name, it should create a private IXStoreDirectory and record the handle in its own boot block.



Ownership of a Store File by a Client

An object initialized with a method from the IXNameAndFileAccess protocol should assume responsibility for freeing the IXStoreFile when it receives a free message.  For this reason, if you want to initialize a number of store file clients by name, you should create an IXStoreDirectory and initialize those clients only through the IXStoreDirectory (by getting their block handles and initializing with initFromBlock:inStore:, or by using IXStoreDirectory's openEntryNamed: method).  These objects should then be freed before the IXStoreDirectory is freed.

The client responsible for freeing the IXStoreFile (or the user of that client) should be sure that the IXStoreFile is in a proper state before actually sending free to it.  This involves sending either abortTransaction or commitTransaction to the IXStoreFile until all transactions are completed, or they'll be effectively aborted when the IXStoreFile is freed.

Note:  Previous versions of this documentation indicated that a store file client didn't have to free its IXStoreFile when freed itself. This is in fact nearly the opposite case from the behavior of standard Indexing Kit classes that conform to the IXNameAndFileAccess protocol, and from the behavior indicated above. You should change any classes you may have written to use the behavior described here.



Temporary Store File Clients

In addition to the methods in this protocol, you may find it convenient to implement a simple init method that initializes the store file client for temporary use by creating an IXStoreFile private to that instance (typically in /tmp).  The temporary instance is then responsible for freeing the run-time store and removing any storage file from the file system when it receives a free message.  In such a case, of course, the store file client will essentially be a regular (nonpersistent) object.



Method Types

Initializing and freeing a client initWithName:inFile:
initFromName:inFile:forWriting:
freeFromStore
+ freeFromName:inFile:
Retrieving the block and store getName:andFile:



Class Methods

freeFromName:inFile:
+ freeFromName:(const char *)aName inFile:(const char *)filename

Removes from filename the storage for the named client.  Normally, your code would have to instantiate a client for the data stored under aName in filename, and send that client a freeFromStore message.  This method provides a convenient way to remove an object from a storage file without allocating and initializing it yourself.  Returns self.

One way to implement this method is to open the root IXStoreDirectory, and have it free the entry with freeEntryNamed: (which in turn sends freeFromBlock:inStore: to the client's class object).  Here's a simple example, without any error handling:

+ freeFromName:(const char *)aName andFile:(const char *)filename
{
IXStoreFile      *theStore;
IXStoreDirectory *theDirectory;

theStore = [[IXStoreFile alloc] initFromFile:filename]
forWriting:YES];
theDirectory = [[IXStoreDirectory alloc] initFromBlock:1
inStore:theStore];
[theDirectory freeEntryNamed:aName];
[theStore commitTransaction];
[theDirectory free];
[theStore free];
return self;
}

Notice that commitTransaction must be sent to the IXStoreFile before freeing it, or the removal won't take effect.

See also:  freeFromStore, freeEntryNamed: (IXStoreDirectory)



Instance Methods

freeFromStore
freeFromStore

Removes the client's data from its IXStoreFile's store file and frees the run-time object.  A store file client's free method simply frees the run-time object without affecting any data in the store file.  Returns nil.

See also:  + freeFromName:inFile:, free (Object)



getName:andFile:
getName:(const char **)aName andFile:(const char **)filename

Returns by reference the name of the client and of the store file in which it keeps its data.  Also returns self.

A store file client must record at least its name (preferably in an instance variable).  The file name can be retrieved from the IXStoreFile by sending it a filename message.  If this is done, the implementation of this method can simply put those values into aName and filename.



initFromName:inFile:forWriting:
initFromName:(const char *)aName
inFile:(const char *)filename
forWriting:(BOOL)flag

Initializes the receiver from data previously stored in filename under entry aName.    That data should have been created by a previous invocation of the initWithName:inFile: method on the original instance of the store file client.  If flag is YES, filename is opened for reading and writing.  If flag is NO, filename is opened for reading only;  changes can be made to the store file's data, but they won't be flushed out to disk.  The receiver isn't required to be of the same class as the original creator of the store data, but it must be able to make sense of it.  Returns self if successful, or nil if the receiver can't be initialized (for example, if aName doesn't exist in filename, or if filename itself doesn't exist).

One way to implement this method is to create an IXStoreDirectory, have it get the block for the entry named aName, and initialize from that block.    Here's a simple example, without any error handling:

- initFromName:(const char *)aName
inFile:(const char *)filename
forWriting:(BOOL)flag
{
IXStoreFile      *theStore;
IXStoreDirectory *theDirectory;
unsigned int      bootBlock;

theStore = [[IXStoreFile alloc] initFromFile:filename
forWriting:flag];
theDirectory = [[IXStoreDirectory alloc] initFromBlock:1
inStore:theStore];
[theDirectory getBlock:&bootBlock ofEntryNamed:aName];

/* Take advantage of the IXBlockAndStoreAccess protocol. */
[self initFromBlock:bootBlock inStore:theStore];

/* Don't free theStore. */
[theDirectory free];

return self;
}

Notice that the IXStoreFile has to be created and retained separately from the IXStoreDirectory, which is freed.  This implementation also assumes that the client conforms to the IXBlockAndStoreAccess protocol (which is in fact incorporated by this IXNameAndFileAccess protocol).

Note:  While a store file client instance exists, it's considered to own its data in the store file.  Your code should never use this method a second time with a specific name unless it's known for certain that any previous instance using that data has been freed (or that both instances will be using the storage for read-only access).  If a second store file client is initialized from the same name as an active client, the data associated with it will probably be corrupted, since there is no means provided for synchronizing changes made by the two instances.

See also:  initWithName:inFile:



initWithName:inFile:
initWithName:(const char *)aName inFile:(const char *)filename

Initializes the receiver to maintain its data in filename (creating the file if necessary) under the name aName.  Returns self, or nil if the receiver can't initialize itself (for example, if a store file client named aName already exists, or if filename couldn't be created).

Here's a simple example implementation, without any error handling:

- initWithName:(const char *)name inFile:(const char *)aFile
{
IXStoreFile      *theStore;
IXStoreDirectory *theDirectory;
IXBTree          *theEntry;

/* Try to open the file first; we have to write to initialize. */
theStore = [[IXStoreFile alloc] initFromFile:aFile
forWriting:YES];

/*
* If the file doesn't exist, create a new one and put an
* IXStoreDirectory in it. Otherwise open the existing
* IXStoreDirectory at block 1.
*/
if (nil == theStore) {
theStore = [[IXStoreFile alloc] initWithFile:aFile];
if (nil == theStore) return [self free];
theDirectory = [[IXStoreDirectory alloc]
initInStore:theStore];
}
else {
theDirectory = [IXStoreDirectory alloc];
[theDirectory initFromBlock:1 inStore:theStore]
}

/*
* Take advantage of the IXBlockAndStoreAccess protocol.
* addEntryNamed:ofClass: will invoke initInStore: on self
* and add the new object to the directory.
*/
self = [theDirectory addEntryNamed:aName ofClass:[self class]];

/*
* See if anything went wrong.
*/
if (nil == self) {
[theStore free];
return nil;
}
else {

/*
* entryName and myStore are instance variables.  All store
* file clients should cache their name and at least the id
* of the IXStoreFile. Be sure to send commitTransaction.
*/
entryName = NXCopyStringBufferFromZone(name,
NXZoneFromPtr(self));
myStore = theStore;

[theStore commitTransaction];
}

/* Don't free the store file. */
[theDirectory free];

return self;
}

See also:  initFromName:inFile:forWriting: