mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-04 08:04:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			537 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			537 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * PortBurn
 | 
						|
 * Mac OS X implementation
 | 
						|
 *
 | 
						|
 * Authors:
 | 
						|
 *   Dominic Mazzoni
 | 
						|
 *
 | 
						|
 * With assistance from Apple's sample code:
 | 
						|
 *   /Developer/Examples/DiscRecording/C/
 | 
						|
 *
 | 
						|
 * License: LGPL
 | 
						|
 */
 | 
						|
 | 
						|
#include "portburn.h"
 | 
						|
#include "portburn_staging.h"
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
 | 
						|
#include <DiscRecording/DiscRecording.h>
 | 
						|
 | 
						|
typedef struct {
 | 
						|
   OSStatus           err;
 | 
						|
   CFArrayRef         deviceList;
 | 
						|
   DRDeviceRef        device;
 | 
						|
   CFMutableArrayRef  trackArray;
 | 
						|
   DRBurnRef          burn;
 | 
						|
   CFArrayRef         trackLayout;
 | 
						|
   void              *staging;
 | 
						|
   float              frac;
 | 
						|
} PBHandle;
 | 
						|
 | 
						|
char *PortBurn_CStringFromCFString(CFStringRef cfname) {
 | 
						|
   char *name;
 | 
						|
   CFIndex len;
 | 
						|
 | 
						|
   len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfname),
 | 
						|
                                           kCFStringEncodingASCII);
 | 
						|
   name = (char *)malloc(len + 1);
 | 
						|
   name[0] = 0;
 | 
						|
   CFStringGetCString(cfname, name, len + 1, kCFStringEncodingASCII);
 | 
						|
   return name;
 | 
						|
}
 | 
						|
 | 
						|
void *PortBurn_Open()
 | 
						|
{   
 | 
						|
   PBHandle *h;
 | 
						|
 | 
						|
   h = (PBHandle *)malloc(sizeof(PBHandle));
 | 
						|
   memset(h, 0, sizeof(PBHandle));
 | 
						|
 | 
						|
   return h;
 | 
						|
}
 | 
						|
 | 
						|
/* Cleanup */
 | 
						|
void PortBurn_Close(void *handle) {
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   if (!h)
 | 
						|
      return;
 | 
						|
 | 
						|
   if (h->deviceList)
 | 
						|
      CFRelease(h->deviceList);
 | 
						|
 | 
						|
   free(h);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* Return a human-readable error string for the last operating system
 | 
						|
   specific error (NOT human readable strings for the PortBurn error
 | 
						|
   codes).  Caller should dispose of the returned string using free(). */
 | 
						|
const char *PortBurn_LastError(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   CFStringRef cferr;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return NULL;
 | 
						|
 | 
						|
   cferr = DRCopyLocalizedStringForDiscRecordingError(h->err);
 | 
						|
   if (cferr) {
 | 
						|
      char *result = PortBurn_CStringFromCFString(cferr);
 | 
						|
      CFRelease(cferr);
 | 
						|
      return result;
 | 
						|
   }
 | 
						|
   else
 | 
						|
      return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* Get the number of devices capable of burning audio CDs.
 | 
						|
   If the result is N, then calls to GetDeviceName and OpenDevice
 | 
						|
   are guaranteed to be valid for indices from 0 up to N-1, until
 | 
						|
   the next time you call GetNumDevices.  At that point, the list of
 | 
						|
   devices will be rescanned, and may be different. */
 | 
						|
int PortBurn_GetNumDevices(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return 0;
 | 
						|
 | 
						|
   if (h->deviceList) {
 | 
						|
      CFRelease(h->deviceList);
 | 
						|
      h->deviceList = NULL;
 | 
						|
   }
 | 
						|
 | 
						|
   h->deviceList = DRCopyDeviceArray();
 | 
						|
   if (!h->deviceList)
 | 
						|
      return 0;
 | 
						|
 | 
						|
   return (int)CFArrayGetCount(h->deviceList);
 | 
						|
}
 | 
						|
 | 
						|
/* Get the name of the device with a given index.  Only valid
 | 
						|
   after a call to GetNumDevices. */
 | 
						|
char *PortBurn_GetDeviceName(void *handle, int index)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   CFDictionaryRef deviceInfo;
 | 
						|
   CFStringRef bus, vendor, product;
 | 
						|
   CFStringRef cfname;
 | 
						|
   char *result;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return NULL;
 | 
						|
 | 
						|
   if (!h->deviceList)
 | 
						|
      return NULL;
 | 
						|
 | 
						|
   DRDeviceRef device;
 | 
						|
   device = (DRDeviceRef)CFArrayGetValueAtIndex(h->deviceList, index);
 | 
						|
 | 
						|
   deviceInfo = DRDeviceCopyInfo(device);
 | 
						|
   bus = (CFStringRef)CFDictionaryGetValue(deviceInfo,
 | 
						|
                                           kDRDevicePhysicalInterconnectKey);
 | 
						|
   if (CFEqual(bus, kDRDevicePhysicalInterconnectFireWire))
 | 
						|
      bus = CFSTR("FireWire: ");
 | 
						|
   else if (CFEqual(bus, kDRDevicePhysicalInterconnectUSB))
 | 
						|
      bus = CFSTR("USB: ");
 | 
						|
   else if (CFEqual(bus, kDRDevicePhysicalInterconnectATAPI))
 | 
						|
      bus = CFSTR("ATAPI: ");
 | 
						|
   else if (CFEqual(bus, kDRDevicePhysicalInterconnectSCSI))
 | 
						|
      bus = CFSTR("SCSI: ");
 | 
						|
   else
 | 
						|
      bus = CFSTR("");
 | 
						|
 | 
						|
   vendor = CFDictionaryGetValue(deviceInfo, kDRDeviceVendorNameKey);
 | 
						|
   product = CFDictionaryGetValue(deviceInfo, kDRDeviceProductNameKey);
 | 
						|
 | 
						|
   cfname = CFStringCreateWithFormat(NULL, NULL,
 | 
						|
                                     CFSTR("%@%@ %@"),
 | 
						|
                                     bus, vendor, product);
 | 
						|
 | 
						|
   result = PortBurn_CStringFromCFString(cfname);
 | 
						|
 | 
						|
   CFRelease(cfname);
 | 
						|
   CFRelease(deviceInfo);
 | 
						|
 | 
						|
   return result;
 | 
						|
}
 | 
						|
 | 
						|
/* Open a particular device by index number.  Returns 0 on success;
 | 
						|
   any nonzero value indicates an error, for example if the device is
 | 
						|
   already open by some other program. */
 | 
						|
int PortBurn_OpenDevice(void *handle, int index)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->deviceList)
 | 
						|
      return pbErrMustCallGetNumDevices;
 | 
						|
 | 
						|
   h->device = (DRDeviceRef)CFArrayGetValueAtIndex(h->deviceList, index);
 | 
						|
 | 
						|
   /* This just indicates interest; it doesn't return an error. */
 | 
						|
   DRDeviceAcquireMediaReservation(h->device);
 | 
						|
 | 
						|
   return pbSuccess;
 | 
						|
}
 | 
						|
 | 
						|
/* Close a device */
 | 
						|
int PortBurn_CloseDevice(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->device)
 | 
						|
      return pbErrDeviceNotOpen;
 | 
						|
 | 
						|
   if (h->burn != NULL) {
 | 
						|
      CFRelease(h->burn);
 | 
						|
      h->burn = NULL;
 | 
						|
   }
 | 
						|
 | 
						|
   if (h->trackArray) {
 | 
						|
      CFRelease(h->trackArray);
 | 
						|
      h->trackArray = NULL;
 | 
						|
   }
 | 
						|
 
 | 
						|
   DRDeviceReleaseMediaReservation(h->device);
 | 
						|
   CFRelease(h->device);
 | 
						|
   h->device = NULL;
 | 
						|
 | 
						|
   if (h->staging) {
 | 
						|
      PortBurn_CleanupStaging(h->staging);
 | 
						|
      h->staging = NULL;
 | 
						|
   }
 | 
						|
 | 
						|
   return pbSuccess;
 | 
						|
}
 | 
						|
 | 
						|
/* Eject the media in the currently opened device */
 | 
						|
int PortBurn_EjectDevice(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->device)
 | 
						|
      return pbErrDeviceNotOpen;
 | 
						|
 | 
						|
   h->err = DRDeviceOpenTray(h->device);
 | 
						|
   if (noErr == h->err)
 | 
						|
      return pbSuccess;  /* success */
 | 
						|
 | 
						|
   h->err = DRDeviceEjectMedia(h->device);
 | 
						|
   if (noErr == h->err)
 | 
						|
      return pbSuccess;  /* success */
 | 
						|
 | 
						|
   return pbErrCannotEject;
 | 
						|
}
 | 
						|
 | 
						|
/* This indicates you're ready to start staging audio data for the
 | 
						|
   currently opened device.  At this point you are committing to
 | 
						|
   exclusive access to the CD burner, and this is the function that
 | 
						|
   will fail if another program is using the device, or if there is
 | 
						|
   no writable CD media in the device at this point.
 | 
						|
   You should pass in the path to a temporary directory that has at
 | 
						|
   least 700 MB of free space, to stage the audio, although note that
 | 
						|
   not all implementations will make use of this directory. */
 | 
						|
int PortBurn_StartStaging(void *handle, const char *tmpdir)
 | 
						|
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   CFDictionaryRef deviceStatus;
 | 
						|
   CFStringRef mediaState;
 | 
						|
   CFDictionaryRef mediaInfo;
 | 
						|
   CFBooleanRef	blank;
 | 
						|
   CFBooleanRef	writable;
 | 
						|
   CFBooleanRef	reserved;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->device)
 | 
						|
      return pbErrDeviceNotOpen;
 | 
						|
 | 
						|
   if (h->staging)
 | 
						|
      return pbErrAlreadyStagingOrBurning;
 | 
						|
 | 
						|
   /* First we check to see that we have blank media in the drive. */
 | 
						|
   deviceStatus = DRDeviceCopyStatus(h->device);
 | 
						|
   mediaState = (CFStringRef)CFDictionaryGetValue(deviceStatus,
 | 
						|
                                                  kDRDeviceMediaStateKey);
 | 
						|
   if (mediaState == NULL) {
 | 
						|
      CFRelease(deviceStatus);
 | 
						|
      return pbErrCannotAccessDevice;
 | 
						|
   }
 | 
						|
 | 
						|
   if (!CFEqual(mediaState, kDRDeviceMediaStateMediaPresent)) {
 | 
						|
      CFRelease(deviceStatus);
 | 
						|
      return pbErrNoMediaInDrive;
 | 
						|
   }
 | 
						|
 | 
						|
   mediaInfo = (CFDictionaryRef)CFDictionaryGetValue(deviceStatus,
 | 
						|
                                                     kDRDeviceMediaInfoKey);
 | 
						|
   blank = (CFBooleanRef)CFDictionaryGetValue(mediaInfo,
 | 
						|
                                              kDRDeviceMediaIsBlankKey);
 | 
						|
 | 
						|
   if (blank == NULL || !CFBooleanGetValue(blank)) {
 | 
						|
      CFRelease(deviceStatus);
 | 
						|
      return pbErrMediaIsNotBlankAndWritable;
 | 
						|
   }
 | 
						|
 | 
						|
   writable = (CFBooleanRef)CFDictionaryGetValue(mediaInfo,
 | 
						|
                                                kDRDeviceMediaIsAppendableKey);
 | 
						|
 | 
						|
   if (writable == NULL || !CFBooleanGetValue(writable)) {
 | 
						|
      CFRelease(deviceStatus);
 | 
						|
      return pbErrMediaIsNotBlankAndWritable;
 | 
						|
   }
 | 
						|
 | 
						|
   reserved = (CFBooleanRef)CFDictionaryGetValue(mediaInfo,
 | 
						|
                                                 kDRDeviceMediaIsReservedKey);
 | 
						|
   if (reserved == NULL || !CFBooleanGetValue(reserved)) {
 | 
						|
      CFRelease(deviceStatus);
 | 
						|
      return pbErrCannotReserveDevice;
 | 
						|
   }
 | 
						|
	
 | 
						|
   CFRelease(deviceStatus);
 | 
						|
 | 
						|
   h->staging = PortBurn_TempDirStaging(tmpdir);
 | 
						|
 | 
						|
   if (!h->staging) {
 | 
						|
      return pbErrCannotCreateStagingDirectory;
 | 
						|
   }
 | 
						|
 | 
						|
   h->trackArray = CFArrayCreateMutable(kCFAllocatorDefault, 0,
 | 
						|
                                        &kCFTypeArrayCallBacks);
 | 
						|
 | 
						|
   return pbSuccess;
 | 
						|
}
 | 
						|
 | 
						|
/* Start a new audio track.  Pass the name of the track, and the
 | 
						|
   length in CD Audio frames (each frame is 1/75.0 of a second, exactly). */
 | 
						|
int PortBurn_StartTrack(void *handle, const char *name, int frames)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->staging)
 | 
						|
      return pbErrMustCallStartStaging;
 | 
						|
 | 
						|
   if (0 == PortBurn_StartStagingTrack(h->staging, name, frames))
 | 
						|
      return pbSuccess;
 | 
						|
   else
 | 
						|
      return pbErrCannotCreateStagingFile;
 | 
						|
}
 | 
						|
 | 
						|
/* Add one frame of audio to the current track.  The buffer must be exactly
 | 
						|
   1176 elements long, containing interleaved left and right audio samples.
 | 
						|
   The values should be signed 16-bit numbers in the native endianness of
 | 
						|
   this computer. */
 | 
						|
int PortBurn_AddFrame(void *handle, short *buffer)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->staging)
 | 
						|
      return pbErrMustCallStartTrack;
 | 
						|
 | 
						|
   if (0 == PortBurn_AddStagingFrame(h->staging, buffer))
 | 
						|
      return pbSuccess;
 | 
						|
   else
 | 
						|
      return pbErrCannotWriteToStagingFile;
 | 
						|
}
 | 
						|
 | 
						|
/* Finish the current audio track. */
 | 
						|
int PortBurn_EndTrack(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   DRAudioTrackRef track;
 | 
						|
   Boolean isDirectory;
 | 
						|
   const char *filename;
 | 
						|
   FSRef fsref;
 | 
						|
   int index;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->staging)
 | 
						|
      return pbErrMustCallStartStaging;
 | 
						|
 | 
						|
   if (0 != PortBurn_EndStagingTrack(h->staging))
 | 
						|
      return pbErrCannotStageTrack;
 | 
						|
 | 
						|
   index = PortBurn_GetNumStagedTracks(h->staging);
 | 
						|
   if (index <= 0)
 | 
						|
      return pbErrCannotStageTrack;
 | 
						|
 | 
						|
   filename = PortBurn_GetStagedFilename(h->staging, index - 1);
 | 
						|
   printf("Filename: '%s'\n", filename);
 | 
						|
   h->err = FSPathMakeRef((const UInt8*)filename, &fsref, &isDirectory);
 | 
						|
   if (h->err != noErr)
 | 
						|
      return pbErrCannotAccessStagedFile;
 | 
						|
   if (isDirectory)
 | 
						|
      return pbErrCannotAccessStagedFile;
 | 
						|
 | 
						|
   track = DRAudioTrackCreate(&fsref);
 | 
						|
 | 
						|
   CFArrayAppendValue(h->trackArray, track);
 | 
						|
   CFRelease(track);
 | 
						|
 | 
						|
   if (track)
 | 
						|
      return pbSuccess;
 | 
						|
   else
 | 
						|
      return pbErrCannotUseStagedFileForBurning;
 | 
						|
}
 | 
						|
 | 
						|
/* Begin burning the disc. */
 | 
						|
int PortBurn_StartBurning(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->device)
 | 
						|
      return pbErrDeviceNotOpen;
 | 
						|
 | 
						|
   h->burn = DRBurnCreate(h->device);
 | 
						|
   if (!h->burn)
 | 
						|
      return pbErrCannotPrepareToBurn;
 | 
						|
 | 
						|
   h->err = DRBurnWriteLayout(h->burn, h->trackArray);
 | 
						|
   if (h->err != noErr) {
 | 
						|
      DRBurnAbort(h->burn);
 | 
						|
      CFRelease(h->burn);
 | 
						|
      h->burn = NULL;
 | 
						|
      return pbErrCannotStartBurning;
 | 
						|
   }
 | 
						|
 | 
						|
   h->frac = 0.0;
 | 
						|
 | 
						|
   return pbSuccess;
 | 
						|
}
 | 
						|
 | 
						|
/* Cancel if burning was in progress.  It might take a while for
 | 
						|
   this to take effect; wait until GetStatus says 1.0 to close
 | 
						|
   the device. */
 | 
						|
int PortBurn_CancelBurning(void *handle)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->burn)
 | 
						|
      return pbErrNotCurrentlyBurning;
 | 
						|
 | 
						|
   DRBurnAbort(h->burn);
 | 
						|
 | 
						|
   return pbSuccess;
 | 
						|
}
 | 
						|
 | 
						|
/* During burning, returns the fraction complete in the given
 | 
						|
   float pointer, from 0.0 to 1.0.  If this function returns
 | 
						|
   nonzero, the disc burning has failed and should be aborted.
 | 
						|
   If *out_fraction_complete is equal to 1.0, the burning is done;
 | 
						|
   you can call PortBurn_CloseDevice.
 | 
						|
*/
 | 
						|
 | 
						|
int PortBurn_GetStatus(void *handle, float *out_fraction_complete)
 | 
						|
{
 | 
						|
   PBHandle *h = (PBHandle *)handle;
 | 
						|
   CFDictionaryRef status;
 | 
						|
   CFStringRef stateRef;
 | 
						|
   CFNumberRef fracRef;
 | 
						|
   CFNumberRef trackNumRef;
 | 
						|
   float frac = 0.0;
 | 
						|
   int trackNum = 0;
 | 
						|
 | 
						|
   *out_fraction_complete = h->frac;
 | 
						|
 | 
						|
   if (!h)
 | 
						|
      return pbErrNoHandle;
 | 
						|
 | 
						|
   if (!h->burn)
 | 
						|
      return pbErrNotCurrentlyBurning;
 | 
						|
 | 
						|
   status = DRBurnCopyStatus(h->burn);
 | 
						|
   if (!status)
 | 
						|
      return pbErrCannotGetBurnStatus;
 | 
						|
 | 
						|
   trackNumRef = (CFNumberRef)
 | 
						|
      CFDictionaryGetValue(status, kDRStatusCurrentTrackKey);
 | 
						|
   if (trackNumRef != NULL) {
 | 
						|
      CFNumberGetValue(trackNumRef, kCFNumberIntType, &trackNum);
 | 
						|
   }
 | 
						|
 | 
						|
   fracRef = (CFNumberRef)
 | 
						|
      CFDictionaryGetValue(status, kDRStatusPercentCompleteKey);
 | 
						|
   if (fracRef != NULL) {
 | 
						|
      CFNumberGetValue(fracRef, kCFNumberFloatType, &frac);
 | 
						|
   }
 | 
						|
 | 
						|
   stateRef = (CFStringRef)
 | 
						|
      CFDictionaryGetValue(status, kDRStatusStateKey);
 | 
						|
   if (stateRef == NULL) {
 | 
						|
      /* Stick with the last percentage */
 | 
						|
      return pbSuccess;
 | 
						|
   }
 | 
						|
 | 
						|
   if (CFEqual(stateRef, kDRStatusStateNone)) {
 | 
						|
      /* Stick with the last percentage */
 | 
						|
      return pbSuccess;
 | 
						|
   }
 | 
						|
 | 
						|
   if (CFEqual(stateRef, kDRStatusStatePreparing)) {
 | 
						|
      /* This takes about 1 second */
 | 
						|
      h->frac = 0.01;
 | 
						|
   }
 | 
						|
 | 
						|
   if (CFEqual(stateRef, kDRStatusStateSessionOpen)) {
 | 
						|
      /* This takes about 3 seconds */
 | 
						|
      h->frac = 0.02;
 | 
						|
   }
 | 
						|
 | 
						|
   if (CFEqual(stateRef, kDRStatusStateTrackOpen) ||
 | 
						|
       CFEqual(stateRef, kDRStatusStateTrackWrite) ||
 | 
						|
       CFEqual(stateRef, kDRStatusStateTrackClose) ||
 | 
						|
       CFEqual(stateRef, kDRStatusStateSessionClose)) {
 | 
						|
      /* The fraction ("percentage") complete will be valid during 
 | 
						|
         the majority of this range */
 | 
						|
      float newFrac = 0.0;
 | 
						|
      if (frac > 0.0 && frac <= 1.0)
 | 
						|
         newFrac = frac;
 | 
						|
 | 
						|
      /* Scale it to the range 0.05 - 0.99 */
 | 
						|
      newFrac = 0.05 + 0.94 * newFrac;
 | 
						|
 | 
						|
      /* Only use that value if it's larger than the previous value */
 | 
						|
      if (newFrac > h->frac)
 | 
						|
         h->frac = newFrac;
 | 
						|
   }
 | 
						|
 | 
						|
   if (CFEqual(stateRef, kDRStatusStateDone)) {
 | 
						|
      /* Returning a fraction complete of 1.0 means we're done! */
 | 
						|
      h->frac = 1.0;
 | 
						|
   }
 | 
						|
 | 
						|
   if (CFEqual(stateRef, kDRStatusStateFailed)) {
 | 
						|
      CFRelease(status);
 | 
						|
      return pbErrBurnFailed;
 | 
						|
   }
 | 
						|
 | 
						|
   *out_fraction_complete = h->frac;
 | 
						|
 | 
						|
   CFRelease(status);   
 | 
						|
   return pbSuccess;
 | 
						|
}
 |