Fragments.c

/*
    File:       Fragments.c
 
    Contains:   Code Fragment manipulation routines
 
    Written by: Chris White 
 
    Copyright:  Copyright © 1995-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                8/5/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
#ifndef __MEMORY__
    #include <Memory.h>
#endif
 
#ifndef __RESOURCES__
    #include <Resources.h>
#endif
 
#ifndef __STDDEF__
    #include <stddef.h>
#endif
 
#ifndef __STRING__
//  #include <string.h>
#endif
 
 
 
#ifndef __FRAGMENTTOOL__
    #include "FragmentTool.h"
#endif
 
#ifndef __FRAGMENTSTUFF__
    #include "FragmentStuff.h"
#endif
 
#include "Prototypes.h"
 
 
#include "Utilities.h"
 
 
 
static OSErr SetInternalResourceSize ( Handle theHan, int16 itemCount );
static OSErr DeleteFileData ( FSSpecPtr theSpec, int32 theOffset, int32 theLength );
 
 
 
 
 
 
//
// Translates the 'cfrg' resource into our own data structure. Ours will
// contain the same information in a simpler format, plus a few extra fields.
//
OSErr ParseResource ( Handle theResource, tHeaderHan privateData )
{
    // Copy the relevant items from the 'cfrg' resource into our internal format
    SignedByte  oldResourceState, oldInternalState;
    short       itemCount, index;
    Ptr         itemStart;
    long        headerSize = offsetof(cfrgHeader, arrayStart);
    OSErr       err = noErr;
    
    
    // set the size of the destination block
    itemCount = (*(hdrHand)theResource)->itemCount;
    err = SetInternalResourceSize ( (Handle) privateData, itemCount );
    if ( err )
        goto done;
    
    // Lock the original resource
    oldResourceState = HGetState ( theResource );
    HLock ( theResource );
    
    oldInternalState = HGetState ( (Handle) privateData );
    HLock ( (Handle) privateData );
    
    // Copy the relevant fields from the header
    (*privateData)->version = (*(hdrHand)theResource)->version;
    (*privateData)->itemCount = itemCount;
    
    // Transfer each item from the cfrg into the internal resource
    if (itemCount == 0) goto done;
    itemStart = &(*(hdrHand)theResource)->arrayStart;
    for (index = 0; index < itemCount; index++) {
        cfrgItem*       srcItem;
        tItemPtr        dstItem;
        
        srcItem = (cfrgItem*)itemStart;
        dstItem = &(*privateData)->itemList[index];
        
        // These are just used internally
        dstItem->bDeleted = false;
        dstItem->bExistsInDocument = true;
        dstItem->tempFilePtr = nil;
        
        // These contain the actual fargement data
        dstItem->archType       = srcItem->archType;
        dstItem->updateLevel    = srcItem->updateLevel;
        dstItem->currVersion    = srcItem->currVersion;
        dstItem->oldDefVersion  = srcItem->oldDefVersion;
        dstItem->appStackSize   = srcItem->appStackSize;
        dstItem->appSubFolder   = srcItem->appSubFolder;
        dstItem->usage          = srcItem->usage;
        dstItem->location       = srcItem->location;
        dstItem->codeOffset     = srcItem->codeOffset;
        dstItem->codeLength     = srcItem->codeLength;
        BlockMove(srcItem->name, dstItem->name, srcItem->name[0]+1);
        
        itemStart += srcItem->itemSize;
    }
 
done:
    
    HSetState ( theResource, oldResourceState );
    HSetState ( (Handle) privateData, oldInternalState );
    
    return err; 
}
 
 
 
//
// Build the 'cfrg' resource from our own data structures.
//
OSErr BuildResource ( tHeaderHan privateData, Handle theResource )
{
    // Construct a cfrg resource from our internal template
    SignedByte      oldResourceState, oldInternalState;
    OSErr           err;
    unsigned long   headerSize =  offsetof(cfrgHeader, arrayStart);
    unsigned long   bytesCopied;
    int             itemCount, deletedCount = 0, index;
 
    // Construct the header by setting the destination handle to that
    // size, clearing the memory, and then inserting the version and
    // item count values
    oldResourceState = HGetState(theResource);
    oldInternalState = HGetState((Handle)privateData);
    HLock((Handle)privateData);
    
    SetHandleSize(theResource, headerSize);
    err = MemError ( );
    if ( err ) goto done;
    
    BlockClear ( *theResource, 0, headerSize );
    
    ((cfrgHeader*)(*theResource))->version = (*privateData)->version;
    itemCount = (*privateData)->itemCount;
    ((cfrgHeader*)(*theResource))->itemCount = itemCount;
    
    // Now, copy each item individually
    bytesCopied = headerSize;
    for (index = 0; index < itemCount; index++)
    {
        cfrgItem*       dstPtr;
        tItemPtr        srcPtr;
        long            itemSize;
        unsigned long   newSize;
        
        srcPtr = &(*privateData)->itemList[index];
        if ( srcPtr->bDeleted )
        {
            deletedCount++;
            continue;
        }
        
        // Calculate the size of this entry
        itemSize = offsetof(cfrgItem, name) + srcPtr->name[0] + 1;
        itemSize += itemSize & 0x0003;  // Pad up to the next multiple of 4
        
        // Extend and clear the handle
        newSize = bytesCopied + itemSize;
        SetHandleSize(theResource, newSize);
        err = MemError ( );
        if ( err ) goto done;
        dstPtr = (cfrgItem*)(((unsigned long)*theResource) + bytesCopied);
        BlockClear ( (Ptr)dstPtr, 0, itemSize );
        
        // Transfer the individual fields
        dstPtr->archType        = srcPtr->archType;
        dstPtr->updateLevel     = srcPtr->updateLevel;
        dstPtr->currVersion     = srcPtr->currVersion;
        dstPtr->oldDefVersion   = srcPtr->oldDefVersion;
        dstPtr->appStackSize    = srcPtr->appStackSize;
        dstPtr->appSubFolder    = srcPtr->appSubFolder;
        dstPtr->usage           = srcPtr->usage;
        dstPtr->location        = srcPtr->location;
        dstPtr->codeOffset      = srcPtr->codeOffset;
        dstPtr->codeLength      = srcPtr->codeLength;
        dstPtr->itemSize        = itemSize;
        BlockMove(srcPtr->name, dstPtr->name, srcPtr->name[0] + 1);
 
        bytesCopied = newSize;
    }
    
    ((cfrgHeader*)(*theResource))->itemCount -= deletedCount;
    
    
done:
    HSetState(theResource, oldResourceState);
    HSetState((Handle)privateData, oldInternalState);
    return err;
}
 
 
 
OSErr CopyFragment ( tHeaderHan sourceHeader, FSSpecPtr sourceSpec, int16 sourceIndex,
                        tHeaderHan targetHeader, FSSpecPtr targetSpec )
{
    SignedByte      sourceState;
    SignedByte      targetState;
    int16           targetIndex = 0;
    OSErr           theErr;
    tItemPtr        targetPtr = nil;
    
    
    // No need to use HLockHi. It's slower, and they're not locked for long
    // enough for it to make any difference here.
    sourceState = HGetState ( (Handle) sourceHeader ); HLock ( (Handle) sourceHeader );
    targetState = HGetState ( (Handle) targetHeader ); HLock ( (Handle) targetHeader );
    
    // Resize the handle based on the new item count
    theErr = SetInternalResourceSize ( (Handle) targetHeader, (*targetHeader)->itemCount + 1 );
    if ( theErr ) goto CleanupAndBail;
    
    // We're appending the data, so the target index is itemCount (Index in zero-based)
    targetIndex = (*targetHeader)->itemCount;
    
    targetPtr = &(*targetHeader)->itemList[targetIndex];
    BlockMoveData ( &(*sourceHeader)->itemList[sourceIndex], targetPtr, sizeof ( tItem ) );
    if ( sourceSpec && targetSpec )
    {
        theErr = AppendFileData ( sourceSpec, targetSpec, &targetPtr->codeOffset, &targetPtr->codeLength );
        if ( theErr ) goto CleanupAndBail;
        
    }
    
    // Success. Now it's safe to update the itemCount
    (*targetHeader)->itemCount++;
    
    return noErr;
    
    
CleanupAndBail:
    
    // Unlock our handles
    HSetState ( (Handle) sourceHeader, sourceState );
    HSetState ( (Handle) targetHeader, targetState );
    // Release any extra storage we may have grabbed
    SetInternalResourceSize ( (Handle) targetHeader, (*targetHeader)->itemCount );
    
    return theErr;
}
 
 
 
//
// This is called twice to actualy delete a fragment. The first time to update
// the data structures we keep in memory. The second time is when the user saves
// the document, and we actually delete the fragment from the data fork.
//
OSErr DeleteFragment ( tHeaderHan theHeader, FSSpecPtr theSpec, int16 theIndex )
{
    SignedByte  theState;
    OSErr       theErr = noErr;
    tItemPtr    theItem = nil;
    
    
    theState = HGetState ( (Handle) theHeader );
    HLock ( (Handle) theHeader );
    
    theItem = GetNthItem ( theHeader, theIndex );
    if ( theItem == nil )
        return kGenericError;
    
    // If the item isn't already marked as deleted, do so
    // and update the header with the new item count.
    if ( !theItem->bDeleted )
        theItem->bDeleted = true;
    
    // If we have a file spec, we need to actually delete the data now.
    if ( theSpec )
    {
        int i;
        
        theErr = DeleteFileData ( theSpec, theItem->codeOffset, theItem->codeLength );
        
        // We need to update the offsets off all the fragments that follow this one
        for ( i = theIndex + 1; i < (*theHeader)->itemCount; i++ )
        {
            tItemPtr    tmpItem;
            
            tmpItem = GetNthItem ( theHeader, i );
            tmpItem->codeOffset -= theItem->codeLength;
        }
    }
    
    HSetState ( (Handle) theHeader, theState );
    
    return noErr;
}
 
 
 
//
// Gets the record pointer given an index
//
tItemPtr GetNthItem ( tHeaderHan theHeader, int16 theIndex )
{
    tItemPtr theItem = nil;
    
    if ( (*theHeader)->itemCount > theIndex )
        theItem = &(*theHeader)->itemList[theIndex];
        
    #if DEBUGGING
    if ( theItem == nil )   DebugStr ( "\p GetNthItem returning nil" );
    #endif
    
    return theItem;
}
 
 
 
//
// Gets the last item. Used for when an item has just been added.
//
tItemPtr GetLastItem ( tHeaderHan theHeader )
{
    int16       theIndex;
    tItemPtr    theItem = nil;
    
    theIndex = (*theHeader)->itemCount - 1;
    theItem = &(*theHeader)->itemList[theIndex];
    
    #if DEBUGGING
    if ( theItem == nil )   DebugStr ( "\p GetLastItem returning nil" );
    #endif
    
    return theItem;
}
 
 
 
//
// Returns the number of fragments currently in the document
//
int16 GetItemCount ( tHeaderHan theHeader )
{
    return (*theHeader)->itemCount;
}
 
 
 
//
// Sets the handle size based on the number of items we pass in
//
static OSErr SetInternalResourceSize ( Handle theHan, int16 itemCount )
{
    SignedByte  theState;
    OSErr       theErr;
    
    // We need to pass SetHandleSize an unlocked handle, but we'll
    // preserve its present state.
    theState = HGetState ( theHan );
    HUnlock ( theHan );
    
    // set the size of the resource based in the item count
    SetHandleSize ( theHan, offsetof ( tHeader, itemList ) +
                                (itemCount * sizeof ( tItem )) );
    theErr = MemError ( );
    HSetState ( theHan, theState );
    
    return theErr;
}
 
 
 
//
// Appends the data in source at given offset of length to target file.
// The offset of the new data in target is returned in offset.
//
OSErr AppendFileData ( FSSpecPtr source, FSSpecPtr target, long* offset, long* length )
{
    OSErr   theErr;
    short   refNum;
    Ptr     theData;
    long    inOutCount;
    
    
    
    //
    // TO DO:
    //      Don't want to depend on having a large enough block
    //      for the entire fragment. Try our heap, then temporary
    //      memory, and finally resort to doing it bit by bit.
    //
    
    theErr = FSpOpenDF ( source, fsRdPerm, &refNum );
    
    // A length of zero indicates Ôuntil the EOFÕ. We'll update
    // the length now, and return it back to the caller.
    if ( *length == 0 )
        theErr = GetEOF ( refNum, length );
    
    theData = NewPtr ( *length );
    if ( theData )
    {
        inOutCount = *length;
        // On entry, ÔoffsetÕ is the offset to the data in source file
        theErr = SetFPos ( refNum, fsFromStart, *offset );
        theErr = FSRead ( refNum, &inOutCount, theData );
        FSClose ( refNum );
        
        FSpOpenDF ( target, fsRdWrPerm, &refNum );
        theErr = SetFPos ( refNum, fsFromLEOF, 0L );
        // On exit, ÔoffsetÕ is the offset to the data in target file
        theErr = GetFPos ( refNum, offset );
        inOutCount = *length;
        theErr = FSWrite ( refNum, &inOutCount, theData );  
        
        // Dispose of the buffer
        DisposePtr ( theData );
    }
    
    // Close the file - ÔsourceÕ if memory alloc failed, ÔtargetÕ if sucessful
    FSClose ( refNum );
    
    
    return noErr;
}
 
 
 
//
//  Deletes the block of data from the data fork at a given offset and length.
//  It achieves this in one of two ways. If the data to be deleted is at the
//  logical EOF, it simply resets the logical EOF to the given offset. If data
//  exists after the data to be deleted, that data is read and writen back at
//  the given offset. 
// 
static OSErr DeleteFileData ( FSSpecPtr theSpec, int32 theOffset, int32 theLength )
{
    OSErr   theErr;
    int16   theRef;
    int32   theEOF;
    int32   inOutCount;
    Ptr     theData;
    
    
    
    theErr = FSpOpenDF ( theSpec, fsRdWrPerm, &theRef );
    if ( theErr )
        return theErr;
    
    theErr = GetEOF ( theRef, &theEOF );
    
    // A length of zero indicates Ôuntil the EOFÕ
    if ( theLength == 0 )
        theLength = theEOF - theOffset;
    
    
    // Is the block to be removed at the EOF? For readablility, I'll keep
    // this ÔifÕ seperate from the above (length == 0) special condition.
    if ( theEOF == theOffset + theLength )
        theErr = SetEOF ( theRef, theOffset );
    else
    {
        // Data exists after the block to be deleted
        theData = NewPtr ( theEOF - (theOffset + theLength) );
        if ( theData )
        {
            theErr = SetFPos ( theRef, fsFromStart, theOffset + theLength );
            inOutCount = theEOF - (theOffset + theLength);
            theErr = FSRead ( theRef, &inOutCount, theData );
            
            theErr = SetFPos ( theRef, fsFromStart, theOffset );
            inOutCount = theEOF - (theOffset + theLength);
            theErr = FSWrite ( theRef, &inOutCount, theData );
            
            theErr = SetEOF ( theRef, theOffset + inOutCount );
        }
    }
    
    FSClose ( theRef );
    
    return noErr;
}
 
 
 
//
// Each fragment's data stored in a temp file has a single temp
// record. It includes a usage count and the fileSpec. If an item
// doesn't have a record, this routine creates it as it increments
// its usage count.
//
OSErr IncrementTempUsageCount ( tItemPtr theItem )
{
    OSErr   theErr;
    
    if ( theItem->tempFilePtr == nil )
    {
        theItem->tempFilePtr = (tTempFilePtr) NewPtrClear ( sizeof ( tTempFileRec ) );
        theErr = MemError ( );
        if ( theErr )
            return theErr;
        
    }
    
    theItem->tempFilePtr->usageCount++;
    
    return noErr;
}
 
 
 
//
// As each document gets its own copy of the fragment, it decrements the
// usage count. Finally, the temp file is deleted and this storage freed.
//
OSErr DecrementTempUsageCount ( tItemPtr theItem )
{   
    OSErr   theErr;
    
    
    if ( theItem->tempFilePtr )
    {
        theItem->tempFilePtr->usageCount--;
        if ( theItem->tempFilePtr->usageCount == 0 )
        {
            theErr = FSpDelete ( &theItem->tempFilePtr->fileSpec );
            if ( theErr )
                return theErr;
                
            DisposePtr ( (Ptr) theItem->tempFilePtr );
            theErr = MemError ( );
            if ( theErr )
                return theErr;
                
        }
        theItem->tempFilePtr = nil;
    }
    
    
    return noErr;
}
 
 
 
//
// Find out how many documents are using this temp record
//
int GetTempUsageCount ( tItemPtr theItem )
{
    int theCount = 0;
    
    if ( theItem->tempFilePtr )
        theCount = theItem->tempFilePtr->usageCount;
        
    return theCount;
}
 
 
 
//
// Get the fileSpec for the fragments data
//
FSSpecPtr GetTempSpecPtr ( tItemPtr theItem )
{
    FSSpecPtr   theSpec = nil;
    
    if ( theItem->tempFilePtr )
        theSpec = &theItem->tempFilePtr->fileSpec;
    
    
    #if DEBUGGING
    if ( theSpec == nil )   DebugStr ( "\p GetTempSpecPtr returning nil" );
    #endif
    
    return theSpec;
}