FDPUtilities.c

/*
    File:       FDPUtilities.c
    
    Description:    utilities used in FinderDragPro.c.  Routines in this file are used in the
                FinderDragPro.c file;  however, since they are not directly related
                to the example, they have been moved here to simplify the main file.
 
    Author:     John Montbriand
 
    Copyright:  Copyright: © 1999 by Apple Computer, Inc.
                all rights reserved.
    
    Disclaimer: You may incorporate this sample code into your applications without
                restriction, though the sample code has been provided "AS IS" and the
                responsibility for its operation is 100% yours.  However, what you are
                not permitted to do is to redistribute the source as "DSC Sample 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 Code, but that you've made changes.
    
    Change History (most recent first):
    9/9/99 created by John Montbriand
*/
 
#include "FDPUtilities.h"
#include <QuickDraw.h>
#include <Gestalt.h>
#include <Palettes.h>
#include <Threads.h>
#include <fp.h>
 
 
 
/* ValidFSSpec verifies that *spec refers is formatted correctly, and it
    verifies that it refers to an existing file in an existing directory on
    and existing volume. If *spec is valid, the function returns noErr,
    otherwise an error is returned. */
OSErr ValidFSSpec(FSSpec *spec) {
    FInfo fndrInfo;
        /* check the name's size */
    if (spec->name[0] + (sizeof(FSSpec) - sizeof(spec->name)) > sizeof(FSSpec)) return paramErr;
        /* ckeck if it refers to a file */
    return FSpGetFInfo(spec, &fndrInfo);
}
 
 
/* ResolveAliasQuietly resolves an alias using a fast search with no user interaction.  Our main loop
    periodically resolves gFileAlias comparing the result to gTargetFile to keep the display up to date.
    As a result, we would like the resolve alias call to be as quick as possible AND since the application
    may be in the background when  it is called, we don't want any user interaction. */
OSErr ResolveAliasQuietly(ConstFSSpecPtr fromFile, AliasHandle alias, FSSpec *target, Boolean *wasChanged) {
    short aliasCount;
    Boolean needsUpdate;
    OSErr err;
        /* set up locals */
    aliasCount = 1;
    needsUpdate = false;
    *wasChanged = false;
        /* call MatchAlias to resolve the alias.  
            kARMNoUI = no user interaction,
            kARMSearch = do a fast search. */
    err = MatchAlias(NULL, (kARMNoUI | kARMSearch), alias, &aliasCount, target, &needsUpdate, NULL, NULL);
    if (err == noErr) {
            /* if the alias was changed, update it. */
        err = UpdateAlias(fromFile, target, alias, wasChanged);
    }
    return err;
}
 
 
 
/* MakeHFSFlavorFromAlias converts an alias handle into a HFSFlavor
    structure filling in the fields with their correct values. */
OSErr MakeHFSFlavorFromAlias(AliasHandle theAlias, HFSFlavor *theFlavor) {
    OSErr err = noErr;
    Boolean wasChanged;
    CInfoPBRec cat;
    
        /* resolve the alias with no user interaction.  Do a fast search. */
    err = ResolveAliasQuietly(NULL, theAlias, &theFlavor->fileSpec, &wasChanged);
    if (err != noErr) return err;
    
        /* pull some information about the file */
    cat.hFileInfo.ioNamePtr = theFlavor->fileSpec.name;
    cat.hFileInfo.ioVRefNum = theFlavor->fileSpec.vRefNum;
    cat.hFileInfo.ioDirID = theFlavor->fileSpec.parID;
    cat.hFileInfo.ioFDirIndex = 0;
    err = PBGetCatInfoSync(&cat);
    if (err != noErr) return err;
    
        /* save the finder flags */
    theFlavor->fdFlags = cat.hFileInfo.ioFlFndrInfo.fdFlags;
 
        /* calculate the type and creator */
    if (theFlavor->fileSpec.parID == fsRtParID) {
            /* volumes have parrent id == 1 */
        theFlavor->fileCreator = 'MACS';
        theFlavor->fileType = 'disk';
    } else if ((cat.hFileInfo.ioFlAttrib & ioDirMask) != 0) {
            /* directories have bit 4 set in the attributes */
        theFlavor->fileCreator = 'MACS';
        theFlavor->fileType = 'fold';
    } else {
            /* assume anything else is a file */
        theFlavor->fileCreator = cat.hFileInfo.ioFlFndrInfo.fdCreator;
        theFlavor->fileType = cat.hFileInfo.ioFlFndrInfo.fdType;
    }
    
        /* done */
    return noErr;
}
 
 
 
 
 
/* IconsToMaskedPixMap converts either an IconServices icon reference or a IconUtilities icon suite into a
    (GWorldPtr, RgnHandle) pair appropriate for dragging as a transparent icon image.  if iconReference
    then calls to IconServices are made, if iconSuite is not null, then calls to icon services are made. 
    The resulting graphics world and region handle are returned in *imageGWorld and *maskRgn. */
OSErr IconsToMaskedPixMap(const Rect *iconRect, Handle iconSuite, IconRef iconReference,
                    GWorldPtr *imageGWorld, RgnHandle *maskRgn) {
    OSErr err;
    GWorldPtr theWorld;
    RgnHandle theMask;
    Rect imageRect;
    Point offsetPt;
    GDHandle saveDevice;
    CGrafPtr savePort;
    PixMapHandle imagePixMap;
    
        /* set up locals */
    theWorld = NULL;
    theMask = NULL;
    imageRect = *iconRect;
    OffsetRect(&imageRect, -imageRect.left, -imageRect.top);
    LocalToGlobal(&offsetPt);
        
        /* create the graphics world */
    err = NewGWorld(&theWorld, 8, &imageRect, NULL, NULL, 0);
    if (err != noErr) goto bail;
    if (theWorld == NULL) { err = memFullErr; goto bail; } /* often missed... */
    
        /* set up the mask region */
    theMask = NewRgn();
    if (theMask == NULL) { err = memFullErr; goto bail; }
    
        /* calculate the icon's mask region */
    if (iconReference != NULL)
        err = IconRefToRgn(theMask, &imageRect, kAlignNone, kIconServicesNormalUsageFlag, iconReference);
    else if (iconSuite != NULL)
        err = IconSuiteToRgn(theMask, &imageRect, kAlignNone, iconSuite);
    else err = paramErr;
    if (err != noErr) goto bail;
    
        /* set up the drawing environment */
    GetGWorld (&savePort, &saveDevice);
    LockPixels((imagePixMap = GetGWorldPixMap(theWorld)));
    SetGWorld(theWorld, NULL);
    
        /* draw the icon suite */
    EraseRect(&imageRect);
    if (iconReference != NULL)
        err = PlotIconRef(&imageRect, kAlignNone, kTransformNone, kIconServicesNormalUsageFlag, iconReference);
    else err = PlotIconSuite(&imageRect, kAlignNone, kTransformNone, iconSuite);
    
        /* restore the drawing environment */
    SetGWorld(savePort, saveDevice);
    UnlockPixels(imagePixMap);
    
        /* after restoring, check drawing result */
    if (err != noErr) goto bail;
    
        /* store what we created. */
    *imageGWorld = theWorld;
    *maskRgn = theMask;
    return noErr;
bail:
    if (theWorld != NULL) DisposeGWorld(theWorld);
    if (theMask != NULL) DisposeRgn(theMask);
    return err;
}
 
 
/* GrayOutBox grays out an area of the screen in the current grafport.  
    *theBox is in local coordinates in the current grafport. This routine
    is for direct screen drawing only.  */
void GrayOutBox(Rect *theBox) {
    long response;
    Rect globalBox;
    GDHandle maxDevice;
    RGBColor rgbWhite = {0xFFFF, 0xFFFF, 0xFFFF}, rgbBlack = {0, 0, 0}, sForground, sBackground;
    PenState penSave;
        /* save the current drawing state */
    GetPenState(&penSave);
        /* if no color quickdraw, fail...*/
    if (Gestalt(gestaltQuickdrawVersion, &response) != noErr) response = 0;
    if (response >= gestalt8BitQD) {
            /* get the device for the rectangle */
        globalBox = *theBox;
        LocalToGlobal((Point*) &globalBox.top);
        LocalToGlobal((Point*) &globalBox.bottom);
        maxDevice = GetMaxDevice(&globalBox);
        if (maxDevice != NULL) {
                /* calculate the best gray */
            if ( GetGray(maxDevice, &rgbWhite, &rgbBlack)) {
                    /* draw over the area in gray using addMax transfer mode */
                GetForeColor(&sForground);
                GetBackColor(&sBackground);
                RGBForeColor(&rgbBlack);
                RGBBackColor(&rgbWhite);
                PenMode(addMax);
                PaintRect(theBox);
                RGBForeColor(&sForground);
                RGBBackColor(&sBackground);
                    /* restore the pen state and leave */
                SetPenState(&penSave);
                return;
            }
        }
    }
        /* fall through to using the gray pattern */
    PenPat(&qd.gray);
    PenMode(notPatBic);
    PaintRect(theBox);
    SetPenState(&penSave);
}
 
 
/* ShowDragHiliteBox is called to hilite the drop box area in the
    main window.  Here, we draw a 3 pixel wide border around *boxBounds.  */
OSErr ShowDragHiliteBox(DragReference theDragRef, Rect *boxBounds) {
    RgnHandle boxRegion, insetRegion;
    OSErr err;
    Rect box;
        /* set up locals */
    boxRegion = insetRegion = NULL;
        /* create the region */
    if ((boxRegion = NewRgn()) == NULL) { err = memFullErr; goto bail; }
    if ((insetRegion = NewRgn()) == NULL) { err = memFullErr; goto bail; }
    box = *boxBounds;
    InsetRect(&box, -5, -5);
    RectRgn(boxRegion, &box);
    InsetRect(&box, 3, 3);
    RectRgn(insetRegion, &box);
    DiffRgn(boxRegion, insetRegion, boxRegion);
        /* hilite the region */
    err = ShowDragHilite(theDragRef, boxRegion, true);
bail:
        /* clean up and leave */
    if (boxRegion != NULL) DisposeRgn(boxRegion);
    if (insetRegion != NULL) DisposeRgn(insetRegion);
    return err;
}
 
 
 
 
 
/* CopyCommandParam includes the parameters used for a copy
    operation.  A record of this type is created in the CopyFileCmd routine,
    and a pointer to it is passed to a Thread Manager task that does
    the actual copy. */
typedef struct {
    FSSpec src, dst;
    short srcRef, dstRef;
    unsigned long total, number;
    CopyCallback callback;
    CopyErrorHandler errorhandler;
    ThreadID copythread;
    Ptr copyBuffer;
} CopyCommandParam, *CopyCmdParamPtr;
 
 
CopyCmdParamPtr gCopyParam = NULL;
 
/* CopyFileInProgress returns true if a copy command is in
    progress.  In this implementation, it simply checks to see
    if gCopyParam is not NULL. */
Boolean CopyFileInProgress(void) {
    return (gCopyParam != NULL);
}
 
/* AbortCopyOperation aborts an ongoing copy operation.  this routine
    deallocates any structures allocated and deletes the target file.  It
    also tears down the thread. */
void AbortCopyOperation(void) {
    if (gCopyParam != NULL) {
        gCopyParam->errorhandler(&gCopyParam->src, userCanceledErr);
        gCopyParam->callback(&gCopyParam->src, kCopyEnd, 100);
        DisposeThread(gCopyParam->copythread, 0, false);
        if (gCopyParam->copyBuffer != NULL)
            DisposePtr((Ptr) gCopyParam->copyBuffer);
        if (gCopyParam->srcRef != 0) FSClose(gCopyParam->srcRef);
        if (gCopyParam->dstRef != 0) FSClose(gCopyParam->dstRef);
        FSpDelete(&gCopyParam->dst);
        DisposePtr((Ptr) gCopyParam);
        gCopyParam = NULL;
    }
}
 
 
/* CopyForkOperation copies forkLength bytes from the source file to the destination
    file using (buffer, bufferSize) as a data buffer during the copy.  As the copy proceeds,
    the value of gBytesCopied is updated to indicate the total number of bytes copied. */
static OSErr CopyForkOperation(CopyCmdParamPtr param, long forkLength) {
    long bytecount, count, percent;
    OSErr err;
    
    bytecount = 0;
    while (bytecount < forkLength) {
        count = forkLength - bytecount;
        if (count > kCopyBufferSize) count = kCopyBufferSize;
        
        err = FSRead(param->srcRef, &count, param->copyBuffer);
        if (err != noErr) return err;
        YieldToAnyThread();
        
        err = FSWrite(param->dstRef, &count, param->copyBuffer);
        if (err != noErr) return err;
        YieldToAnyThread();
            
        bytecount += count;
        param->number += count;
        if (param->total == 0)
            percent = 100;
        else percent = rinttol((((float) param->number) / ((float) param->total)) * 100.0);
        param->callback(&param->src, kCopyRun, percent);
        YieldToAnyThread();
 
    }
    return noErr;
}
 
/* CopyCommandThread is a Thread Manager task that completes a copy operation.
    The task is started in the CopyFileCmd routine.   */
static pascal void* CopyCommandThread(void *threadParam) {
    CopyCmdParamPtr param;
    CInfoPBRec cat;
    OSErr err;
        /* set up */
    param = (CopyCmdParamPtr) threadParam;
    YieldToAnyThread();
        /* calculate the total bytes */
    cat.hFileInfo.ioNamePtr = param->src.name;
    cat.hFileInfo.ioVRefNum = param->src.vRefNum;
    cat.hFileInfo.ioDirID = param->src.parID;
    cat.hFileInfo.ioFDirIndex = 0;
    err = PBGetCatInfoSync(&cat);
    if (err != noErr) goto bail;
    if ((cat.hFileInfo.ioFlAttrib & ioDirMask) != 0) {
        err = kCannotCopyDirError;
        goto bail;
    }
    param->total = cat.hFileInfo.ioFlLgLen + cat.hFileInfo.ioFlRLgLen;
    
        /* file created, switch out */
    YieldToAnyThread();
 
        /* allocate the copy buffer */
    param->copyBuffer = NewPtr(kCopyBufferSize);
    if (param->copyBuffer == NULL) { err = memFullErr; goto bail; }
 
        /* copy the data fork, if there is one */
    if (cat.hFileInfo.ioFlLgLen > 0) {
        err = FSpOpenDF(&param->src, fsRdPerm, &param->srcRef);
        if (err != noErr) goto bail;
        err = FSpOpenDF(&param->dst, fsWrPerm, &param->dstRef);
        if (err != noErr) goto bail;
        err = CopyForkOperation(param, cat.hFileInfo.ioFlLgLen);
        if (err != noErr) goto bail;
        FSClose(param->srcRef);
        param->srcRef = 0;
        FSClose(param->dstRef);
        param->dstRef = 0;
    }
        /* copy the resource fork, if there is one */
    if (cat.hFileInfo.ioFlRLgLen > 0) {
        err = FSpOpenRF(&param->src, fsRdPerm, &param->srcRef);
        if (err != noErr) goto bail;
        err = FSpOpenRF(&param->dst, fsWrPerm, &param->dstRef);
        if (err != noErr) goto bail;
        err = CopyForkOperation(param, cat.hFileInfo.ioFlRLgLen);
        if (err != noErr) goto bail;
        FSClose(param->srcRef);
        param->srcRef = 0;
        FSClose(param->dstRef);
        param->dstRef = 0;
    }
        /* flush the destination volume */
    FlushVol(NULL, param->dst.vRefNum); /* err = best effort */ 
        /* clean up */
    param->callback(&param->src, kCopyEnd, 100);
    DisposePtr(param->copyBuffer);
    param->copyBuffer = NULL;
    DisposePtr((Ptr) param);
    gCopyParam = NULL;
    return NULL;
bail:
    param->errorhandler(&param->src, err);
    param->callback(&param->src, kCopyEnd, 100);
    if (param->srcRef != 0) FSClose(param->srcRef);
    if (param->dstRef != 0) FSClose(param->dstRef);
    if (param->copyBuffer != NULL) DisposePtr(param->copyBuffer);
    FSpDelete(&param->dst);
    DisposePtr((Ptr) param);
    gCopyParam = NULL;
    return NULL;
}
 
OSErr CopyFileCmd(FSSpec *theSource, FSSpec *theTarget, CopyCallback callback, CopyErrorHandler errorhandler) {
    CopyCmdParamPtr param;
    OSErr err;
    Boolean threadCreated;
        /* set up locals */
    threadCreated = true;
    param = NULL;
        /* check copy conditions */
    if (gCopyParam != NULL) return kCopyNotRentrantError;
        /* set up param */
    param = (CopyCmdParamPtr) NewPtr(sizeof(CopyCommandParam));
    if (param == NULL) { err = memFullErr; goto bail; }
    param->src = *theSource;
    param->dst = *theTarget;
    param->srcRef = 0;
    param->dstRef = 0;
    param->total = param->number = 0;
    param->callback = callback;
    param->errorhandler = errorhandler;
    param->copyBuffer = NULL;
        /* create a thread */
    err = NewThread(kCooperativeThread, CopyCommandThread, param, 0, kCreateIfNeeded, NULL, &param->copythread);
    if (err != noErr) goto bail;
    threadCreated = true;
    gCopyParam = param;
    callback(&param->src, kCopyStart, 0);
    return noErr;
bail:
    if (threadCreated)
        DisposeThread(param->copythread, 0, false);
    if (param != NULL) DisposePtr((Ptr) param);
    return err;
}