DSoRecord.m   [plain text]


/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*!
 * @header DSoRecord
 */


#import "DSoRecord.h"
#import "DSoRecordPriv.h"

#import "DSoNode.h"
#import "DSoBuffer.h"
#import "DSoDataNode.h"
#import "DSoDataList.h"
#import "DSoGroup.h"
#import "DSoUser.h"
#import "DSoException.h"
#import "DSoAttributeUtils.h"

@class DSoUser, DSoGroup;

#define DS_API_IS_UNSUPPORTED(err)  (err == eNotHandledByThisNode \
                                     || err == eNotYetImplemented \
                                     || err == eNoLongerSupported \
                                     || err == eUnknownAPICall) 

@implementation DSoRecord

// ----------------------------------------------------------------------------
//	¥ DSRecord Public Instance Methods
// ----------------------------------------------------------------------------
#pragma mark **** Public Instance Methods ****

// ctor & dtor

- (id)init
{
    [super init];
    mDirectory  = nil;
    mParent		= nil;
    mName		= nil;
    mRecRef		= 0;
    return self;
}

- (DSoRecord*)initInNode:(DSoNode*)inParent recordRef:(tRecordReference)inRecRef type:(const char*)inType
{
	DSoRecord *returnRec = self;
	// If the type is a known type for which we have a more specialized sub-class,
	// then return an object of that type.
	if (!strcmp(inType, kDSStdRecordTypeUsers))
		returnRec = [[DSoUser alloc] initInNode:inParent recordRef:inRecRef];
	else if (!strcmp(inType, kDSStdRecordTypeGroups))
		returnRec = [[DSoGroup alloc] initInNode:inParent recordRef:inRecRef];
	else
	{
		[self initInNode:inParent recordRef:inRecRef];
		mType = [[NSString alloc] initWithCString:inType];
	}
	if (returnRec != self)
		[self release];
	
    return returnRec;
}

- (DSoRecord*)initInNode:(DSoNode*)inParent type:(const char*)inType name:(NSString*)inName
{
	return [self initInNode:inParent type:inType name:inName create:YES];
}

- (void)dealloc
{
    if (mRecRef)
        dsCloseRecord(mRecRef);
    [mParent release];
    [mName release];
    [mType release];
    [super dealloc];
}

- (void)finalize
{
    if (mRecRef)
        dsCloseRecord(mRecRef);
    [super finalize];
}

// Inline accessors.
- (NSString*)getName
{
    return mName;
}

- (const char*)getType
{
    return [mType UTF8String];
}

- (DSoNode*)node
{
    return mParent;
}

	// Casting operators.
- (tRecordReference)dsRecordReference
{
    return mRecRef;
}

/******************
 * attributeCount *
 ******************/
- (unsigned long)attributeCount
{
    tDirStatus		nError		= eDSNoErr;
    tRecordEntryPtr recordInfo  = nil;
    unsigned long   count		= 0;
    
    if (nError = dsGetRecordReferenceInfo(mRecRef, &recordInfo))
        [DSoException raiseWithStatus:nError];
    
    count = recordInfo->fRecordAttributeCount;
    
    if (nError = dsDeallocRecordEntry([[mParent directory] dsDirRef], recordInfo))
        [DSoException raiseWithStatus:nError];
    
    return count;
}
 
/****************
 * GetAttribute *
 ****************/

- (NSArray*)getAllAttributes
{
    return [self _getAllAttributesIncludeValues:NO];
}

- (NSDictionary*)getAllAttributesAndValues
{
    return [self _getAllAttributesIncludeValues:YES];
}

- (NSDictionary*)getAttributes:(NSArray*)inAttributes
{
    return [self _getAttributes:inAttributes includeValues:YES];
}

- (NSString*)getAttribute:(const char*)inAttributeType
{
    return [self getAttribute: inAttributeType allowBinary: NO];
}

- (id)getAttribute:(const char*)inAttributeType allowBinary:(BOOL)inAllowBinary
{
	id  returnValue = nil;

	// we want to catch the exception here, as the user is just asking for the attribute.
	// If there isn't one, we should just return nil
	@try
    {
		returnValue = [self getAttribute: inAttributeType index: 1 allowBinary: inAllowBinary];
    } @catch( NSException *exception ) {
        // ignore all exceptions
    }
	
	return returnValue;
}

- (NSString*)getAttribute:(const char*)inAttributeType index:(unsigned long)inIndex
{
    return [self getAttribute: inAttributeType index: inIndex allowBinary: NO];
}

- (id)getAttribute:(const char*)inAttributeType index:(unsigned long)inIndex allowBinary:(BOOL)inAllowBinary
{
	tDirStatus				err ;
	DSRef					dirRef = [mDirectory verifiedDirRef];
	tAttributeValueEntryPtr	pAttrVal = nil;
    DSoDataNode				*type = nil;
    NSString				*sValue = nil;
    
    type = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
    err = dsGetRecordAttributeValueByIndex (mRecRef, [type dsDataNode], inIndex, &pAttrVal);
    [type release];
    if (err)
        [DSoException raiseWithStatus:err];
    
    sValue = [DSoAttributeUtils getAttributeFromBuffer: &pAttrVal->fAttributeValueData allowBinary: inAllowBinary];
    dsDeallocAttributeValueEntry (dirRef, pAttrVal) ;
    return sValue;
}

/*********************
 * GetAttributeRange *
 *********************/
// get a list of attribute values of given type in given range
- (NSArray*)getAttribute:(const char*)inAttributeType range:(NSRange)inRange
{
    return [self getAttribute: inAttributeType range: inRange allowBinary: NO];
}

- (NSArray*)getAttribute:(const char*)inAttributeType range:(NSRange)inRange allowBinary:(BOOL)inAllowBinary
{
	tDirStatus					err			= eDSNoErr;
	DSRef						dirRef		= [mDirectory verifiedDirRef];
	tAttributeValueEntryPtr		pAttrVal	= nil;
    unsigned long				i			= 0;
    DSoDataNode				   *type		= nil;
    NSMutableArray			   *valueList   = [[NSMutableArray alloc] initWithCapacity:inRange.length];
    
    type = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];

    for (i = inRange.location; i < (inRange.location + inRange.length); i++)
    {
        err = dsGetRecordAttributeValueByIndex (mRecRef, [type dsDataNode], i, &pAttrVal);
        if (err)
        {
            [valueList release];
            [type release];
            [DSoException raiseWithStatus:err];
        }
        
        [valueList addObject: [DSoAttributeUtils getAttributeFromBuffer: &pAttrVal->fAttributeValueData allowBinary: inAllowBinary]];
        dsDeallocAttributeValueEntry (dirRef, pAttrVal) ;
        pAttrVal = nil;
    }
    [type release];
    return [valueList autorelease];
}


/*************************
* getAttributeValueCount *
**************************/
- (unsigned long)getAttributeValueCount:(const char*)inAttributeType
{
    tAttributeEntryPtr		pAttrEntry  = nil;
    DSoDataNode			   *attrType	= [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
    tDirStatus				err			= eDSNoErr;
    unsigned long			valueCount  = 0;

    err = dsGetRecordAttributeInfo(mRecRef, [attrType dsDataNode], &pAttrEntry);
    [attrType release];
    if (err)
        [DSoException raiseWithStatus:err];
    valueCount = pAttrEntry->fAttributeValueCount;
    dsDeallocAttributeEntry([mDirectory dsDirRef], pAttrEntry);
    return valueCount;
}

/*****************
 * SetAttributes *
 *****************/
- (void)setAttribute:(const char*)inAttributeType value:(id)inAttributeValue
{
	// this call can throw, make an autorelease array so it gets released
    [self setAttribute:inAttributeType values:[NSArray arrayWithObject:inAttributeValue]];
}

- (void)setAttribute:(const char*)inAttributeType values:(NSArray*)inAttributeValues
{
    tDirStatus				err				= eDSNoErr;
    DSoDataNode			   *attrType		= nil;
    DSoDataNode			   *attrValue		= nil;
    DSoDataList			   *attrValuesList  = nil;
    register unsigned int   i				= 0;
    unsigned long			count			= [inAttributeValues count];
    tAttributeEntryPtr		entryPtr		= nil;
    tAttributeValueEntryPtr attrPtr			= nil;
    tDataNodePtr			attrTypeNodePtr = NULL;
    DSRef					dirRef			= 0;

    // Make sure the directory is valid.
    
    dirRef = [mDirectory verifiedDirRef];
    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
    attrTypeNodePtr = [attrType dsDataNode]; // convenience pointer to avoid a lot of method calls.
    
    if ([mParent supportsSetAttributeValues])
    {
        attrValuesList = [[DSoDataList alloc] initWithDir: mDirectory values: inAttributeValues];
        err = dsSetAttributeValues (mRecRef, attrTypeNodePtr, [attrValuesList dsDataList]);
        [attrValuesList release];
        if (DS_API_IS_UNSUPPORTED(err)) 
        {
            // not supported by this node, fall back to the old approach
            [mParent setSupportsSetAttributeValues:NO];
        }
        else if (err != eDSEmptyDataList)
        {
            [attrType release];
            if (err)
                [DSoException raiseWithStatus:err];
            
            dsFlushRecord( mRecRef );
            return;
        }
    }
	
    // initialize the first value:
    attrValue = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory value:[inAttributeValues objectAtIndex:0]];
    
	// For simplicity, try to just remove current attribute if it exists
    @try
    {
        err = dsRemoveAttribute (mRecRef, attrTypeNodePtr) ;
        // If the attribute can't be removed then try to delete all but the
        // first value and replace the first value with the new first value
        if (err) {
            
            err = dsGetRecordAttributeInfo (mRecRef, attrTypeNodePtr, (tAttributeEntryPtr *) &entryPtr);
            if (!err && entryPtr->fAttributeValueCount > 0)
            {
                // Delete all but the first value.
                for (i = 2 ; i <= entryPtr->fAttributeValueCount; i++)
                {
                    err = dsGetRecordAttributeValueByIndex (mRecRef, attrTypeNodePtr, 1, (tAttributeValueEntryPtr *) &attrPtr);
                    if (!err)
                    {
                        err = dsRemoveAttributeValue(mRecRef, attrTypeNodePtr,
                                                     attrPtr->fAttributeValueID);
                        dsDeallocAttributeValueEntry(dirRef, attrPtr);
                        attrPtr = nil;
                    }
                    if (err)
                        [DSoException raiseWithStatus:err];
                }
                // Now replace the first value with the new first value
                err = dsGetRecordAttributeValueByIndex (mRecRef, attrTypeNodePtr, 1, (tAttributeValueEntryPtr *) &attrPtr);
                if (!err)
                {
					NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Want this method to be memory contained
                    id              firstObject = [inAttributeValues objectAtIndex:0];
                    const char      *newFirstValue = NULL;
                    unsigned long   newFirstValueLen = 0;
                    tAttributeValueEntryPtr newValueEntry;
                    
                    if( [firstObject isKindOfClass:[NSString class]] )
                    {
                        newFirstValue = [firstObject UTF8String];
                        newFirstValueLen = strlen( newFirstValue );
                    }
                    else if( [firstObject isKindOfClass:[NSData class]] )
                    {
                        newFirstValue = [firstObject bytes];
                        newFirstValueLen = [firstObject length];
                    }
                    else
                    {
                        @throw [NSException exceptionWithName: NSInvalidArgumentException reason: @"[DSoRecord setAttribute:values:] values contained non NSData nor NSString" userInfo: nil];
                    }

					newValueEntry = dsAllocAttributeValueEntry(dirRef, attrPtr->fAttributeValueID, (void *)newFirstValue, newFirstValueLen );
					// We're done with attrPtr, dealloc it
                    dsDeallocAttributeValueEntry(dirRef, attrPtr);
					attrPtr = nil;

					// Set the first value, then dealloc the valueEntry item.
                    err = dsSetAttributeValue(mRecRef, attrTypeNodePtr, newValueEntry);
					dsDeallocAttributeValueEntry(dirRef, newValueEntry);
					[pool drain];
                }
                if (err)
                    [DSoException raiseWithStatus:err];
            }
            else {
                // if there was an error getting the record attribute info, then maybe the attribute
                // didn't ever exist and we can just add it.
                err = dsAddAttribute (mRecRef, attrTypeNodePtr, 0, [attrValue dsDataNode]);
                if (err)
                    [DSoException raiseWithStatus:err];
            }
            dsDeallocAttributeEntry(dirRef, entryPtr);
            entryPtr = nil;
        }
        else
        {
            // The former attribute was successfully removed, add the first new attribute.
            err = dsAddAttribute (mRecRef, [attrType dsDataNode], 0, [attrValue dsDataNode]);
            if (err)
                [DSoException raiseWithStatus:err];
        }
    } @catch( NSException *exception ) {
        [attrType release]; // need to release this cause we don't release until the end normally
        @throw;
    } @finally {
        if (attrPtr != nil)
            dsDeallocAttributeValueEntry(dirRef, attrPtr);
        if (entryPtr != nil)
            dsDeallocAttributeEntry(dirRef, entryPtr);
        [attrValue release];
    }
    
    for (i = 1; i < count; i++)
    {
        DSoDataNode *dnValue = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory value:[inAttributeValues objectAtIndex:i]];
        err = dsAddAttributeValue (mRecRef, attrTypeNodePtr, [dnValue dsDataNode]);
        [dnValue release];
        if (err)
        {
            [attrType release];
            [DSoException raiseWithStatus:err];
        }
    }
    
    dsFlushRecord( mRecRef );
    
    [attrType release];
}

/*****************
 * AddAttributes *
 *****************/
// Add each attribute to the attribute list; if that particular attribute value
// exists and mergeValues == true, don't add a duplicate
- (void)addAttribute:(const char*)inAttributeType values:(NSArray*)inAttributeValues
{
    [self addAttribute:inAttributeType values:inAttributeValues mergeValues:YES];
}

- (void)addAttribute:(const char*)inAttributeType values:(NSArray*)inAttributeValues mergeValues:(BOOL)inMergVals
{
	register unsigned int		i			= 0;
    tDirStatus					err			= eDSNoErr ;
    DSoDataNode				   *attrType	= nil;
    unsigned long				count		= [inAttributeValues count];
    
    [mDirectory verifiedDirRef];
    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];

	for (i = 0; i < count; i++)
    {
		if (!inMergVals
            || ![self attributeExists:inAttributeType withValue:[inAttributeValues objectAtIndex:i]]) {
			DSoDataNode	*dnValue;
            dnValue = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory value:[inAttributeValues objectAtIndex:i]];
			err = dsAddAttributeValue (mRecRef, [attrType dsDataNode], [dnValue dsDataNode]);
            [dnValue release];
            if (err)
            {
                [attrType release];
                [DSoException raiseWithStatus:err];
            }
		}
    }

    dsFlushRecord( mRecRef );

    [attrType release];
}

/*******************
 * ChangeAttribute *
 *******************/
// change the attribute whose value is inAttrValue to inNewAttrValue
- (void)changeAttribute:(const char*)inAttributeType oldValue:(NSString*)inAttrValue newValue:(id)inNewAttrValue
{
	tDirStatus					err			= eDSNoErr;
	tAttributeValueEntryPtr		attrValuePtr= nil;
	tAttributeValueEntryPtr		newValue	= nil;
	DSRef						dirRef		= [mDirectory verifiedDirRef];
    DSoDataNode				   *attrType	= nil;
	unsigned long				newValueLen = 0;
    const char                 *newValuePtr = NULL;
    
    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
	@try
    {
		attrValuePtr = [self getAttrValuePtrForTypeNode:attrType value:inAttrValue];
    } @catch (NSException *exception ) {
		[attrType release];
        @throw;
    }
    
    if( [inNewAttrValue isKindOfClass:[NSString class]] )
    {
        newValuePtr = [inNewAttrValue UTF8String];
        newValueLen = strlen( newValuePtr );
    }
    else if( [inNewAttrValue isKindOfClass:[NSData class]] )
    {
        newValuePtr = [inNewAttrValue bytes];
        newValueLen = [inNewAttrValue length];
    }
    else
    {
		[attrType release];
        @throw [NSException exceptionWithName: NSInvalidArgumentException reason: @"[DSoRecord changeAttribute:oldValue:newValue:] new value was not NSData nor NSString" userInfo: nil];
    }
    
    newValue = dsAllocAttributeValueEntry( dirRef, attrValuePtr->fAttributeValueID, (void *)newValuePtr, newValueLen ) ;
	dsDeallocAttributeValueEntry (dirRef, attrValuePtr) ;
	if (newValue == nil)
    {
        [attrType release];
        [DSoException raiseWithStatus:eDSAllocationFailed];
    }
	err = dsSetAttributeValue (mRecRef, [attrType dsDataNode], newValue);
    [attrType release];
	dsDeallocAttributeValueEntry (dirRef, newValue) ;
    if (err)
        [DSoException raiseWithStatus:err];
    
    dsFlushRecord( mRecRef );
}

- (void)changeAttribute:(const char*)inAttributeType index:(unsigned int)inIndex newValue:(id)inNewAttrValue
{
	tDirStatus					err			= eDSNoErr;
	tAttributeValueEntryPtr		attrValuePtr= nil;
	tAttributeValueEntryPtr		newValue	= nil ;
	DSRef						dirRef		= [mDirectory verifiedDirRef];
    DSoDataNode				   *attrType	= nil;
	const char				   *newValuePtr = NULL;
	unsigned long				newValueLen = 0;
    
    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
	err = dsGetRecordAttributeValueByIndex (mRecRef, [attrType dsDataNode],
													inIndex, &attrValuePtr);
    if (err)
    {
        [attrType release];
        [DSoException raiseWithStatus:err];
    }

    if( [inNewAttrValue isKindOfClass:[NSString class]] )
    {
        newValuePtr = [inNewAttrValue UTF8String];
        newValueLen = strlen( newValuePtr );
    }
    else if( [inNewAttrValue isKindOfClass:[NSData class]] )
    {
        newValuePtr = [inNewAttrValue bytes];
        newValueLen = [inNewAttrValue length];
    }
    else
    {
        [attrType release];
        @throw [NSException exceptionWithName: NSInvalidArgumentException reason: @"[DSoRecord changeAttribute:index:newValue:] value was not NSData nor NSString" userInfo: nil];
    }
    
    newValue = dsAllocAttributeValueEntry( dirRef, attrValuePtr->fAttributeValueID, (void *)newValuePtr, newValueLen ) ;
	dsDeallocAttributeValueEntry (dirRef, attrValuePtr) ;
	if (newValue == nil)
    {
        [attrType release];
        [DSoException raiseWithStatus:eDSAllocationFailed];
    }
	err = dsSetAttributeValue (mRecRef, [attrType dsDataNode], newValue);
    [attrType release];
    
	dsDeallocAttributeValueEntry (dirRef, newValue) ;

    if (err)
        [DSoException raiseWithStatus:err];
    
    dsFlushRecord( mRecRef );
}

/*******************
 * AttributeExists *
 *******************/
- (BOOL)attributeExists:(const char*)inAttributeType withValue:(id)inAttributeValue
{
	DSRef						dirRef		= [mDirectory verifiedDirRef];
	DSoDataNode				   *attrType	= nil;
	tAttributeValueEntryPtr		attrPtr		= nil;
	BOOL						bExists		= NO ;

	if (!inAttributeValue || [inAttributeValue length] == 0)
		return NO;

    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
    @try
    {
		attrPtr = [self getAttrValuePtrForTypeNode:attrType value:inAttributeValue];
		dsDeallocAttributeValueEntry (dirRef, attrPtr) ;
		bExists = YES ;
    } @catch (NSException *exception ) {
        // ignore any exceptions here.
    }

    [attrType release];
    return bExists;
}

- (void)removeAttribute:(const char*)inAttributeType
{
    DSoDataNode    *attrType	= nil;
    tDirStatus		nError		= eDSNoErr;

    if (!inAttributeType || inAttributeType == "")
        return;
    
    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
    nError = dsRemoveAttribute (mRecRef, [attrType dsDataNode]) ;
    [attrType release];
    if (nError)
        [DSoException raiseWithStatus:nError];
    dsFlushRecord( mRecRef );
}

- (void)removeAttribute:(const char*)inAttributeType value:(id)inAttributeValue
{
    // this call can throw, make an autorelease array so it gets released
    [self removeAttribute:inAttributeType values:[NSArray arrayWithObject:inAttributeValue]];
}

- (void)removeAttribute:(const char*)inAttributeType values:(NSArray*)inAttributeValues
{
    tDirStatus					nError			= eDSNoErr;
    tAttributeValueEntryPtr		attrValuePtr	= nil;
    DSRef						dirRef			= [mDirectory verifiedDirRef];
    DSoDataNode				   *attrType		= nil;
    int							i				= 0;
	int							cnt				= 0;

    if (inAttributeValues == nil)
        return;
    
    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
	cnt = [inAttributeValues count];
    for (i = 0; i < cnt; i++)
    {
		@try
        {
			attrValuePtr = [self getAttrValuePtrForTypeNode:attrType value:[inAttributeValues objectAtIndex:i]];
        } @catch( NSException *exception ) {
			[attrType release];
            @throw;
        }
		
        nError = dsRemoveAttributeValue (mRecRef, [attrType dsDataNode], attrValuePtr->fAttributeValueID);
        dsDeallocAttributeValueEntry (dirRef, attrValuePtr) ;
    }
    [attrType release];
    if (nError)
        [DSoException raiseWithStatus:nError];
    dsFlushRecord( mRecRef );
}

- (void)removeAttribute:(const char*)inAttributeType index:(unsigned int)inIndex
{
    tDirStatus					nError			= eDSNoErr;
    tAttributeValueEntryPtr		attrValuePtr	= nil;
    DSRef						dirRef			= [mDirectory verifiedDirRef];
    DSoDataNode				   *attrType		= nil;

    attrType = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inAttributeType];
    nError = dsGetRecordAttributeValueByIndex (mRecRef, [attrType dsDataNode],
                                            inIndex, &attrValuePtr);
    if (nError)
    {
        [attrType release];
        [DSoException raiseWithStatus:nError];
    }

    nError = dsRemoveAttributeValue (mRecRef, [attrType dsDataNode], attrValuePtr->fAttributeValueID);
    dsDeallocAttributeValueEntry (dirRef, attrValuePtr);
    [attrType release];
    if (nError)
        [DSoException raiseWithStatus:nError];
    dsFlushRecord( mRecRef );
}

- (void)removeRecord
{
    tDirStatus		err ;
	
    if (err = dsDeleteRecord (mRecRef))
        [DSoException raiseWithStatus:err];
    // mRecRef will now be invalid... should I set it to 0 to trigger any asserts on anything that tries to use it?
}

@end
#pragma mark

// ----------------------------------------------------------------------------
// Protected Instance Methods
// ----------------------------------------------------------------------------
#pragma mark **** Protected Instance Methods ****

@implementation DSoRecord (DSoRecordPrivate)

- (DSoRecord*)initInNode:(DSoNode*)inParent recordRef:(tRecordReference)inRecRef
{
    [self init];
    mParent		= [inParent retain];
    mDirectory  = [inParent directory];
    mRecRef		= inRecRef;
    mName		= [[self getAttribute:kDSNAttrRecordName] retain];
    return self;
}

- (DSoRecord*)initInNode:(DSoNode*)inParent type:(const char*)inType name:(NSString*)inName create:(BOOL)inShouldCreate
{
    tDirStatus nError;

    [self init];

    mDirectory = [inParent directory];
	if (inShouldCreate)
	{
		DSoDataNode *type, *name;
		type = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory cString:inType];
		name = [(DSoDataNode*)[DSoDataNode alloc] initWithDir:mDirectory string:inName];
		nError = dsCreateRecordAndOpen ([inParent dsNodeReference], [type dsDataNode],
								  [name dsDataNode], &mRecRef) ;
		[type release];
		[name release];
		if (nError)
			[DSoException raiseWithStatus:nError];
	}
    mName = [inName retain];
    mParent = [inParent retain];
    mType = [[NSString alloc] initWithCString:inType];
    
    if( [mType isEqualToString:@kDSStdRecordTypeUsers] ||
        [mType isEqualToString:@kDSStdRecordTypeGroups] ||
        [mType isEqualToString:@kDSStdRecordTypeComputerLists] ||
        [mType isEqualToString:@kDSStdRecordTypeComputers] )
    {
        CFUUIDRef uuidRef = CFUUIDCreate( NULL );
        NSString* uuidString = [(NSString*)CFUUIDCreateString( NULL, uuidRef ) autorelease];
        CFRelease( uuidRef );
        [self setAttribute:kDS1AttrGeneratedUID value:uuidString];
    }
    
    return self;
}

/*
 * _getAllAttributesIncludeValues:
 *
 * This method makes the following assumptions:
 * 1) The short names (RecordName) of all records in the node for this record
 *	are unique.
 * 2) dsGetRecordList() will always return first the record whose RecordName 
 *	matches the record search name criteria, and any records whose RealNames
 *	match the search name critera will never be returned first.
 */
- (id)_getAllAttributesIncludeValues:(BOOL)inIncludeVals
{
    return [self _getAttributes:[NSArray arrayWithObject:@kDSAttributesAll] includeValues:inIncludeVals];
}

- (id)_getAttributes:(NSArray*)inAttributes includeValues:(BOOL)inIncludeVals
{
    tContextData 		localcontext	= 0;
    tRecordEntryPtr		pRecEntry		= nil;
    tAttributeListRef	attrListRef		= 0;
    DSoBuffer		   *recordBuf		= nil;
    DSoDataList		   *recName			= [(DSoDataList*)[DSoDataList alloc] initWithDir:mDirectory string:mName];
    DSoDataList		   *recType			= [(DSoDataList*)[DSoDataList alloc] initWithDir:mDirectory string:mType];
    DSoDataList		   *attrType		= [(DSoDataList*)[DSoDataList alloc] initWithDir:mDirectory values:inAttributes];
    id					userAttributes  = nil;
    tDirStatus 			err				= eDSNoErr;
    UInt32              returnCount		= 0;

    // default to an 8k buffer initially, the inline DS buffer is 16k so plenty of room
    recordBuf = [[DSoBuffer alloc] initWithDir:mDirectory bufferSize:8192];

    do {
        err = dsGetRecordList([mParent dsNodeReference], [recordBuf dsDataBuffer], [recName dsDataList], eDSExact, [recType dsDataList], [attrType dsDataList], !inIncludeVals, &returnCount, &localcontext);
        if (!err && returnCount > 0)
        {
            err = dsGetRecordEntry([mParent dsNodeReference],[recordBuf dsDataBuffer],1,&attrListRef, &pRecEntry );
            if (inIncludeVals)
            {
                userAttributes = [DSoAttributeUtils getAttributesAndValuesInNode: mParent
													fromBuffer: recordBuf
													listReference: attrListRef
													count: pRecEntry->fRecordAttributeCount];
            }
            else
            {
                userAttributes = [DSoAttributeUtils getAttributesInNode: mParent
													fromBuffer: recordBuf
													listReference: attrListRef
													count: pRecEntry->fRecordAttributeCount];
            }
			if (pRecEntry != NULL)
			{
				dsDeallocRecordEntry([mDirectory dsDirRef], pRecEntry);
			}
			if (attrListRef != 0)
			{
				dsCloseAttributeList(attrListRef);
			}
        }
        else if (err == eDSBufferTooSmall)
        {
            [recordBuf grow:2 * [recordBuf getBufferSize]];
        }
    } while (err == eDSBufferTooSmall || (err == eDSNoErr && returnCount == 0 && localcontext != 0));
    
    if (localcontext != 0)
        dsReleaseContinueData([mParent dsNodeReference], localcontext);
    
    [recordBuf release];
    [recName release];
    [attrType release];
    [recType release];
    if (err)
        [DSoException raiseWithStatus:err];
    else if (returnCount < 1)
        [[DSoException name:@"eDSInvalidRecordName"
                     reason:@"DSoRecord's getAllAttributes failed because more than one matching name was found in the node.  This should never happen."
                     status:eDSInvalidRecordName] raise];
    
    return userAttributes;
}

/***************************
* _GetAttrValuePtrByValue *
***************************/
- (tAttributeValueEntryPtr) getAttrValuePtrForTypeNode:(DSoDataNode*)inAttrType value:(id)inAttrValue
{
    register unsigned int	i = 1 ;
    tDirStatus				err ;
    tAttributeEntryPtr		entryPtr ;
    tAttributeValueEntryPtr	attrPtr ;
    DSRef					dirRef = [mDirectory dsDirRef];
    const char              *pAttrValue = nil;
    int                     pAttrValueLen = 0;

    if( [inAttrValue isKindOfClass:[NSString class]] ) {
        pAttrValue = [inAttrValue UTF8String];
        pAttrValueLen = strlen( pAttrValue );
    } else if( [inAttrValue isKindOfClass:[NSData class]] ) {
        pAttrValue = [inAttrValue bytes];
        pAttrValueLen = [inAttrValue length];
    } else {
        @throw [NSException exceptionWithName: NSInvalidArgumentException reason: @"[DSoRecord getAttrValuePtrForTypeNode:] value was not NSString nor NSData" userInfo: nil];
    }

    if (err = dsGetRecordAttributeInfo (mRecRef, [inAttrType dsDataNode], &entryPtr))
        [DSoException raiseWithStatus:err];

#warning can use dsGetAttributeValueByValue instead of looping values

    while (i <= entryPtr->fAttributeValueCount) {
        // walk through the list of values for this attribute
        if (err = dsGetRecordAttributeValueByIndex (mRecRef, [inAttrType dsDataNode], i++,
                                                    &attrPtr))
            continue ;
        // if the lengths are the same and the contents are the same
        if (pAttrValueLen == attrPtr->fAttributeValueData.fBufferLength && 
            memcmp(pAttrValue, attrPtr->fAttributeValueData.fBufferData, attrPtr->fAttributeValueData.fBufferLength) == 0) {
    
            dsDeallocAttributeEntry (dirRef, entryPtr) ;
            return attrPtr ;
        }
        dsDeallocAttributeValueEntry (dirRef, attrPtr) ;
    }
    dsDeallocAttributeEntry (dirRef, entryPtr) ;
    [DSoException raiseWithStatus:eDSAttributeNotFound];
    return nil;   // For the sake of the compiler not complaining
}


@end