1 /*
2  * PortBurn
3  * Mac OS X implementation
4  *
5  * Authors:
6  *   Dominic Mazzoni, Leland Lucius
7  *
8  * With assistance from Apple's sample code:
9  *   /Developer/Examples/DiscRecording/C/
10  *
11  * License: LGPL
12  */
13 
14 #include "portburn.h"
15 #include "portburn_staging.h"
16 
17 #include <string.h>
18 
19 #include <DiscRecording/DiscRecording.h>
20 
21 typedef struct {
22    OSStatus           err;
23    CFArrayRef         deviceList;
24    DRDeviceRef        device;
25    CFMutableArrayRef  trackArray;
26    DRBurnRef          burn;
27    DREraseRef         erase;
28    void              *staging;
29    float              frac;
30    int                verify;
31    int                underrun;
32    int                speed;
33    int                test;
34    int                eject;
35    bool               gapless;
36 } PBHandle;
37 
PortBurn_CStringFromCFString(CFStringRef cfname)38 char *PortBurn_CStringFromCFString(CFStringRef cfname)
39 {
40    char *name;
41    CFIndex len;
42 
43    len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfname),
44                                            kCFStringEncodingASCII);
45    name = (char *) malloc(len + 1);
46    if (name == NULL) {
47       return NULL;
48    }
49 
50    name[0] = 0;
51    CFStringGetCString(cfname, name, len + 1, kCFStringEncodingASCII);
52 
53    return name;
54 }
55 
PortBurn_Open()56 void *PortBurn_Open()
57 {
58    PBHandle *h;
59 
60    h = (PBHandle *) calloc(1, sizeof(PBHandle));
61    if (h == NULL) {
62       return NULL;
63    }
64 
65    h->frac = 0.0;
66 
67    h->test = pbTestDefault;
68    h->verify = pbVerifyDefault;
69    h->underrun = pbUnderrunDefault;
70    h->eject = pbEjectDefault;
71    h->gapless = pbGaplessDefault;
72    h->speed = pbSpeedDefault;
73 
74    return h;
75 }
76 
77 /* Cleanup */
PortBurn_Close(void * handle)78 void PortBurn_Close(void *handle)
79 {
80    PBHandle *h = (PBHandle *)handle;
81 
82    if (h == NULL) {
83       return;
84    }
85 
86    if (h->device) {
87       PortBurn_CloseDevice(h);
88    }
89 
90    if (h->deviceList) {
91       CFRelease(h->deviceList);
92    }
93 
94    free(h);
95 }
96 
97 
98 /* Return a human-readable error string for the last operating system
99    specific error (NOT human readable strings for the PortBurn error
100    codes).  Caller should dispose of the returned string using free(). */
PortBurn_LastError(void * handle)101 char *PortBurn_LastError(void *handle)
102 {
103    PBHandle *h = (PBHandle *)handle;
104    CFStringRef cferr;
105    char *result = NULL;
106 
107    if (h == NULL) {
108       return NULL;
109    }
110 
111    cferr = DRCopyLocalizedStringForDiscRecordingError(h->err);
112    if (cferr) {
113       result = PortBurn_CStringFromCFString(cferr);
114       CFRelease(cferr);
115    }
116 
117    return result;
118 }
119 
120 /* Get the number of devices capable of burning audio CDs.
121    If the result is N, then calls to GetDeviceName and OpenDevice
122    are guaranteed to be valid for indices from 0 up to N-1, until
123    the next time you call GetNumDevices.  At that point, the list of
124    devices will be rescanned, and may be different. */
PortBurn_GetNumDevices(void * handle)125 int PortBurn_GetNumDevices(void *handle)
126 {
127    PBHandle *h = (PBHandle *)handle;
128 
129    if (h == NULL) {
130       return 0;
131    }
132 
133    if (h->deviceList != NULL) {
134       CFRelease(h->deviceList);
135       h->deviceList = NULL;
136    }
137 
138    h->deviceList = DRCopyDeviceArray();
139    if (h->deviceList == NULL) {
140       return 0;
141    }
142 
143    return (int) CFArrayGetCount(h->deviceList);
144 }
145 
146 /* Get the name of the device with a given index.  Only valid
147    after a call to GetNumDevices. */
PortBurn_GetDeviceName(void * handle,int index)148 char *PortBurn_GetDeviceName(void *handle, int index)
149 {
150    PBHandle *h = (PBHandle *)handle;
151    CFDictionaryRef deviceInfo;
152    CFStringRef bus;
153    CFStringRef vendor;
154    CFStringRef product;
155    CFStringRef cfname;
156    DRDeviceRef device;
157    char *result;
158 
159    if (h == NULL) {
160       return NULL;
161    }
162 
163    if (h->deviceList == NULL) {
164       return NULL;
165    }
166 
167    if (index < 0 || index >= (int) CFArrayGetCount(h->deviceList)) {
168       return NULL;
169    }
170 
171    device = (DRDeviceRef) CFArrayGetValueAtIndex(h->deviceList, index);
172 
173    deviceInfo = DRDeviceCopyInfo(device);
174    if (deviceInfo == NULL) {
175       return NULL;
176    }
177 
178    bus = (CFStringRef)CFDictionaryGetValue(deviceInfo,
179                                            kDRDevicePhysicalInterconnectKey);
180 
181    if (CFEqual(bus, kDRDevicePhysicalInterconnectFireWire)) {
182       bus = CFSTR("FireWire: ");
183    }
184    else if (CFEqual(bus, kDRDevicePhysicalInterconnectUSB)) {
185       bus = CFSTR("USB: ");
186    }
187    else if (CFEqual(bus, kDRDevicePhysicalInterconnectATAPI)) {
188       bus = CFSTR("ATAPI: ");
189    }
190    else if (CFEqual(bus, kDRDevicePhysicalInterconnectSCSI)) {
191       bus = CFSTR("SCSI: ");
192    }
193    else {
194       bus = CFSTR("");
195    }
196 
197    vendor = (CFStringRef)
198       CFDictionaryGetValue(deviceInfo, kDRDeviceVendorNameKey);
199    product = (CFStringRef)
200       CFDictionaryGetValue(deviceInfo, kDRDeviceProductNameKey);
201 
202    cfname = CFStringCreateWithFormat(NULL, NULL,
203                                      CFSTR("%@%@ %@"),
204                                      bus, vendor, product);
205    CFRelease(deviceInfo);
206 
207    if (cfname == NULL) {
208       return NULL;
209    }
210 
211    result = PortBurn_CStringFromCFString(cfname);
212 
213    CFRelease(cfname);
214 
215    return result;
216 }
217 
218 /* Open a particular device by index number.  Returns 0 on success;
219    any nonzero value indicates an error, for example if the device is
220    already open by some other program. */
PortBurn_OpenDevice(void * handle,int index)221 int PortBurn_OpenDevice(void *handle, int index)
222 {
223    PBHandle *h = (PBHandle *)handle;
224 
225    if (h == NULL) {
226       return pbErrNoHandle;
227    }
228 
229    if (h->deviceList == NULL) {
230       return pbErrMustCallGetNumDevices;
231    }
232 
233    if (h->device != NULL) {
234       return pbErrDeviceAlreadyOpen;
235    }
236 
237    if (index < 0 || index >= (int) CFArrayGetCount(h->deviceList)) {
238       return pbErrInvalidDeviceIndex;
239    }
240 
241    h->device = (DRDeviceRef) CFArrayGetValueAtIndex(h->deviceList, index);
242 
243    /* This just indicates interest; it doesn't return an error. */
244    DRDeviceAcquireMediaReservation(h->device);
245 
246    return pbSuccess;
247 }
248 
249 /* Close a device */
PortBurn_CloseDevice(void * handle)250 int PortBurn_CloseDevice(void *handle)
251 {
252    PBHandle *h = (PBHandle *)handle;
253 
254    if (h == NULL) {
255       return pbErrNoHandle;
256    }
257 
258    if (h->device == NULL) {
259       return pbErrDeviceNotOpen;
260    }
261 
262    if (h->burn != NULL) {
263       DRBurnAbort(h->burn);
264       CFRelease(h->burn);
265       h->burn = NULL;
266    }
267 
268    if (h->erase != NULL) {
269       CFRelease(h->erase);
270       h->erase = NULL;
271    }
272 
273    if (h->trackArray != NULL) {
274       CFRelease(h->trackArray);
275       h->trackArray = NULL;
276    }
277 
278    if (h->staging != NULL) {
279       PortBurn_CleanupStaging(h->staging);
280       h->staging = NULL;
281    }
282 
283    DRDeviceReleaseMediaReservation(h->device);
284 
285    h->device = NULL;
286 
287    h->frac = 0.0;
288 
289    h->test = pbTestDefault;
290    h->verify = pbVerifyDefault;
291    h->underrun = pbUnderrunDefault;
292    h->eject = pbEjectDefault;
293    h->gapless = pbGaplessDefault;
294    h->speed = pbSpeedDefault;
295 
296    return pbSuccess;
297 }
298 
299 /* Eject the media in the currently opened device */
PortBurn_EjectDevice(void * handle)300 int PortBurn_EjectDevice(void *handle)
301 {
302    PBHandle *h = (PBHandle *)handle;
303 
304    if (h == NULL) {
305       return pbErrNoHandle;
306    }
307 
308    if (h->device == NULL) {
309       return pbErrDeviceNotOpen;
310    }
311 
312    h->err = DRDeviceOpenTray(h->device);
313    if (h->err == noErr) {
314       return pbSuccess;  /* success */
315    }
316 
317    h->err = DRDeviceEjectMedia(h->device);
318    if (h->err == noErr) {
319       return pbSuccess;  /* success */
320    }
321 
322    return pbErrCannotEject;
323 }
324 
325 /* Erase the media in the currently opened device */
PortBurn_StartErasing(void * handle,int type)326 int PortBurn_StartErasing(void *handle, int type)
327 {
328    CFMutableDictionaryRef props;
329 
330    PBHandle *h = (PBHandle *)handle;
331 
332    if (h == NULL) {
333       return pbErrNoHandle;
334    }
335 
336    if (h->device == NULL) {
337       return pbErrDeviceNotOpen;
338    }
339 
340    if (h->burn != NULL || h->erase != NULL) {
341       return pbErrCannotStartErasing;
342    }
343 
344    h->erase = DREraseCreate(h->device);
345    if (h->erase == NULL) {
346       return pbErrCannotPrepareToErase;
347    }
348 
349    props = CFDictionaryCreateMutableCopy(NULL, 0, DREraseGetProperties(h->erase));
350    if (props == NULL) {
351       CFRelease(h->erase);
352       h->erase = NULL;
353       return pbErrCannotPrepareToErase;
354    }
355 
356    CFDictionaryAddValue(props,
357                         kDREraseTypeKey,
358                         type ? kDREraseTypeComplete : kDREraseTypeQuick);
359 
360    DREraseSetProperties(h->erase, props);
361 
362    CFRelease(props);
363 
364    h->frac = 0.0;
365 
366    h->err = DREraseStart(h->erase);
367    if (h->err != noErr) {
368       CFRelease(h->erase);
369       h->erase = NULL;
370       return pbErrCannotStartErasing;
371    }
372 
373    return pbSuccess;
374 }
375 
376 /*
377 */
378 
PortBurn_GetEraseStatus(void * handle,float * out_fraction_complete)379 int PortBurn_GetEraseStatus(void *handle, float *out_fraction_complete)
380 {
381    PBHandle *h = (PBHandle *)handle;
382    CFDictionaryRef status;
383    CFStringRef stateRef;
384    CFNumberRef fracRef;
385    float frac = 0.0;
386 
387    *out_fraction_complete = h->frac;
388 
389    if (h == NULL) {
390       return pbErrNoHandle;
391    }
392 
393    if (h->erase == NULL) {
394       return pbErrNotCurrentlyErasing;
395    }
396 
397    status = DREraseCopyStatus(h->erase);
398    if (status == NULL) {
399       return pbErrCannotGetEraseStatus;
400    }
401 
402    fracRef = (CFNumberRef) CFDictionaryGetValue(status, kDRStatusPercentCompleteKey);
403    if (fracRef != NULL) {
404       CFNumberGetValue(fracRef, kCFNumberFloatType, &frac);
405    }
406 
407    stateRef = (CFStringRef) CFDictionaryGetValue(status, kDRStatusStateKey);
408    if (stateRef == NULL) {
409       /* Stick with the last percentage */
410       return pbSuccess;
411    }
412 
413    if (CFEqual(stateRef, kDRStatusStateNone)) {
414       /* Stick with the last percentage */
415       return pbSuccess;
416    }
417 
418    if (CFEqual(stateRef, kDRStatusStateErasing)) {
419       /* The fraction ("percentage") complete will be valid during
420          the majority of this range */
421       float newFrac = 0.0;
422       if (frac > 0.0 && frac <= 1.0) {
423          newFrac = frac;
424       }
425 
426       /* Scale it to the range 0.05 - 0.99 */
427       newFrac = 0.05 + 0.94 * newFrac;
428 
429       /* Only use that value if it's larger than the previous value */
430       if (newFrac > h->frac) {
431          h->frac = newFrac;
432       }
433    }
434 
435    if (CFEqual(stateRef, kDRStatusStateDone)) {
436       /* Returning a fraction complete of 1.0 means we're done! */
437       h->frac = 1.0;
438       CFRelease(h->erase);
439       h->erase = NULL;
440    }
441 
442    if (CFEqual(stateRef, kDRStatusStateFailed)) {
443       CFRelease(status);
444       return pbErrEraseFailed;
445    }
446 
447    *out_fraction_complete = h->frac;
448 
449    CFRelease(status);
450 
451    return pbSuccess;
452 }
453 
454 /* */
PortBurn_GetMediaState(void * handle,int * state)455 int PortBurn_GetMediaState(void *handle, int *state)
456 {
457    PBHandle *h = (PBHandle *)handle;
458    CFDictionaryRef deviceStatus;
459    CFStringRef mediaState;
460    CFDictionaryRef mediaInfo;
461    CFBooleanRef	bref;
462    int mstate = pbMediaNotWritable;
463 
464    if (h == NULL) {
465       return pbErrNoHandle;
466    }
467 
468    if (h->device == NULL) {
469       return pbErrDeviceNotOpen;
470    }
471 
472    /* First we check to see that we have blank media in the drive. */
473    deviceStatus = DRDeviceCopyStatus(h->device);
474    if (deviceStatus == NULL) {
475       return pbErrCannotAccessDevice;
476    }
477 
478    mediaState = (CFStringRef) CFDictionaryGetValue(deviceStatus,
479                                                    kDRDeviceMediaStateKey);
480    if (mediaState == NULL) {
481       CFRelease(deviceStatus);
482       return pbErrCannotAccessDevice;
483    }
484 
485    if (!CFEqual(mediaState, kDRDeviceMediaStateMediaPresent)) {
486       CFRelease(deviceStatus);
487       *state = pbMediaNone;
488       return pbSuccess;
489    }
490 
491    mediaInfo = (CFDictionaryRef) CFDictionaryGetValue(deviceStatus,
492                                                       kDRDeviceMediaInfoKey);
493    bref = (CFBooleanRef) CFDictionaryGetValue(mediaInfo,
494                                               kDRDeviceMediaIsBlankKey);
495 
496    if (bref != NULL && CFBooleanGetValue(bref)) {
497       mstate |= pbMediaBlank;
498    }
499 
500    bref = (CFBooleanRef) CFDictionaryGetValue(mediaInfo,
501                                               kDRDeviceMediaIsErasableKey);
502 
503    if (bref != NULL && CFBooleanGetValue(bref)) {
504       mstate |= pbMediaErasable;
505    }
506 
507    bref = (CFBooleanRef) CFDictionaryGetValue(mediaInfo,
508                                               kDRDeviceMediaIsAppendableKey);
509 
510    if (bref != NULL && CFBooleanGetValue(bref)) {
511       mstate |= pbMediaAppendable;
512    }
513 
514    bref = (CFBooleanRef) CFDictionaryGetValue(mediaInfo,
515                                               kDRDeviceMediaIsOverwritableKey);
516 
517    if (bref != NULL && CFBooleanGetValue(bref)) {
518       mstate |= pbMediaOverwritable;
519    }
520 
521    CFRelease(deviceStatus);
522 
523    *state = mstate;
524 
525    return pbSuccess;
526 }
527 
528 /* This indicates you're ready to start staging audio data for the
529    currently opened device.  At this point you are committing to
530    exclusive access to the CD burner, and this is the function that
531    will fail if another program is using the device, or if there is
532    no writable CD media in the device at this point.
533    You should pass in the path to a temporary directory that has at
534    least 700 MB of free space, to stage the audio, although note that
535    not all implementations will make use of this directory. */
PortBurn_StartStaging(void * handle,const char * tmpdir)536 int PortBurn_StartStaging(void *handle, const char *tmpdir)
537 {
538    PBHandle *h = (PBHandle *)handle;
539    int state;
540    int rc;
541 
542    if (h == NULL) {
543       return pbErrNoHandle;
544    }
545 
546    if (h->device == NULL) {
547       return pbErrDeviceNotOpen;
548    }
549 
550    if (h->staging != NULL) {
551       return pbErrAlreadyStagingOrBurning;
552    }
553 
554    rc = PortBurn_GetMediaState(h, &state);
555    if (rc != pbSuccess) {
556       return rc;
557    }
558 
559    /* check to see that we have blank media in the drive. */
560    if (!(state & pbMediaBlank)) {
561       return pbErrMediaIsNotBlank;
562    }
563 
564    h->staging = PortBurn_TempDirStaging(tmpdir);
565    if (h->staging == NULL) {
566       return pbErrCannotCreateStagingDirectory;
567    }
568 
569    h->trackArray = CFArrayCreateMutable(kCFAllocatorDefault, 0,
570                                         &kCFTypeArrayCallBacks);
571 
572    return pbSuccess;
573 }
574 
575 /* Start a new audio track.  Pass the name of the track, and the
576    length in CD Audio frames (each frame is 1/75.0 of a second, exactly). */
PortBurn_StartTrack(void * handle,const char * name)577 int PortBurn_StartTrack(void *handle, const char *name)
578 {
579    PBHandle *h = (PBHandle *)handle;
580 
581    if (h == NULL) {
582       return pbErrNoHandle;
583    }
584 
585    if (h->staging == NULL) {
586       return pbErrMustCallStartStaging;
587    }
588 
589    if (PortBurn_StartStagingTrack(h->staging, name, FALSE) != 0) {
590       return pbErrCannotCreateStagingFile;
591    }
592 
593    return pbSuccess;
594 }
595 
596 /* Add one frame of audio to the current track.  The buffer must be exactly
597    1176 elements long, containing interleaved left and right audio samples.
598    The values should be signed 16-bit numbers in the native endianness of
599    this computer. */
PortBurn_AddFrame(void * handle,short * buffer)600 int PortBurn_AddFrame(void *handle, short *buffer)
601 {
602    PBHandle *h = (PBHandle *)handle;
603 
604    if (h == NULL) {
605       return pbErrNoHandle;
606    }
607 
608    if (h->staging == NULL) {
609       return pbErrMustCallStartStaging;
610    }
611 
612    if (PortBurn_AddStagingFrame(h->staging, buffer) != 0) {
613       return pbErrCannotWriteToStagingFile;
614    }
615 
616    return pbSuccess;
617 }
618 
619 /* Finish the current audio track. */
PortBurn_EndTrack(void * handle)620 int PortBurn_EndTrack(void *handle)
621 {
622    PBHandle *h = (PBHandle *)handle;
623    DRAudioTrackRef track;
624    Boolean isDirectory;
625    const char *filename;
626    FSRef fsref;
627    int index;
628 
629    if (h == NULL) {
630       return pbErrNoHandle;
631    }
632 
633    if (h->staging == NULL) {
634       return pbErrMustCallStartStaging;
635    }
636 
637    if (PortBurn_EndStagingTrack(h->staging) != 0) {
638       return pbErrCannotStageTrack;
639    }
640 
641    index = PortBurn_GetNumStagedTracks(h->staging);
642    if (index <= 0) {
643       return pbErrCannotStageTrack;
644    }
645 
646    filename = PortBurn_GetStagedFilename(h->staging, index - 1);
647    if (filename == NULL) {
648       return pbErrCannotAccessStagedFile;
649    }
650 
651    h->err = FSPathMakeRef((const UInt8*)filename, &fsref, &isDirectory);
652    if (h->err != noErr) {
653       return pbErrCannotAccessStagedFile;
654    }
655 
656    if (isDirectory) {
657       return pbErrCannotAccessStagedFile;
658    }
659 
660    track = DRAudioTrackCreate(&fsref);
661    if (track == NULL) {
662       return pbErrCannotUseStagedFileForBurning;
663    }
664 
665    if (h->gapless) {
666       CFMutableDictionaryRef props;
667 
668       props = CFDictionaryCreateMutableCopy(NULL, 0, DRTrackGetProperties(track));
669       if (props == NULL) {
670          CFRelease(track);
671          return pbErrCannotUseStagedFileForBurning;
672       }
673 
674       SInt64 gap = 0;
675       CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt64Type, &gap);
676       if (num != NULL) {
677          CFDictionarySetValue(props,
678                               kDRPreGapLengthKey,
679                               num);
680          CFRelease(num);
681       }
682 
683       DRTrackSetProperties(track, props);
684 
685       CFRelease(props);
686    }
687 
688    CFArrayAppendValue(h->trackArray, track);
689 
690    CFRelease(track);
691 
692    return pbSuccess;
693 }
694 
695 /* Begin burning the disc. */
PortBurn_StartBurning(void * handle)696 int PortBurn_StartBurning(void *handle)
697 {
698    CFMutableDictionaryRef props;
699 
700    PBHandle *h = (PBHandle *)handle;
701 
702    if (h == NULL) {
703       return pbErrNoHandle;
704    }
705 
706    if (h->device == NULL) {
707       return pbErrDeviceNotOpen;
708    }
709 
710    if (h->burn != NULL || h->erase != NULL) {
711       return pbErrCannotStartBurning;
712    }
713 
714    h->burn = DRBurnCreate(h->device);
715    if (h->burn == NULL) {
716       return pbErrCannotPrepareToBurn;
717    }
718 
719    props = CFDictionaryCreateMutableCopy(NULL, 0, DRBurnGetProperties(h->burn));
720    if (props == NULL) {
721       DRBurnAbort(h->burn);
722       CFRelease(h->burn);
723       h->burn = NULL;
724       return pbErrCannotPrepareToBurn;
725    }
726 
727    CFDictionaryAddValue(props,
728                         kDRBurnCompletionActionKey,
729                         h->eject ? kDRBurnCompletionActionEject : kDRBurnCompletionActionMount);
730    CFDictionaryAddValue(props,
731                         kDRBurnVerifyDiscKey,
732                         h->verify ? kCFBooleanTrue : kCFBooleanFalse);
733    CFDictionaryAddValue(props,
734                         kDRBurnUnderrunProtectionKey,
735                         h->underrun ? kCFBooleanTrue : kCFBooleanFalse);
736    CFDictionaryAddValue(props,
737                         kDRBurnTestingKey,
738                         h->test ? kCFBooleanTrue : kCFBooleanFalse);
739 
740    if (h->speed != pbSpeedDefault) {
741       float speed;
742       if (h->speed == pbSpeedMax) {
743          speed = kDRDeviceBurnSpeedMax;
744       }
745       else {
746          speed = h->speed * kDRDeviceBurnSpeedCD1x;
747       }
748 
749       CFNumberRef num = CFNumberCreate(NULL, kCFNumberFloatType, &speed);
750       if (num != NULL) {
751          CFDictionaryAddValue(props,
752                               kDRBurnRequestedSpeedKey,
753                               num);
754          CFRelease(num);
755       }
756    }
757 
758    DRBurnSetProperties(h->burn, props);
759 
760    CFRelease(props);
761 
762    h->frac = 0.0;
763 
764    h->err = DRBurnWriteLayout(h->burn, h->trackArray);
765    if (h->err != noErr) {
766       DRBurnAbort(h->burn);
767       CFRelease(h->burn);
768       h->burn = NULL;
769       return pbErrCannotStartBurning;
770    }
771 
772    return pbSuccess;
773 }
774 
775 /* Cancel if burning was in progress.  It might take a while for
776    this to take effect; wait until GetStatus says 1.0 to close
777    the device. */
PortBurn_CancelBurning(void * handle)778 int PortBurn_CancelBurning(void *handle)
779 {
780    PBHandle *h = (PBHandle *)handle;
781 
782    if (h == NULL) {
783       return pbErrNoHandle;
784    }
785 
786    if (h->burn == NULL) {
787       return pbErrNotCurrentlyBurning;
788    }
789 
790    DRBurnAbort(h->burn);
791 
792    return pbSuccess;
793 }
794 
795 /* During burning, returns the fraction complete in the given
796    float pointer, from 0.0 to 1.0.  If this function returns
797    nonzero, the disc burning has failed and should be aborted.
798    If *out_fraction_complete is equal to 1.0, the burning is done;
799    you can call PortBurn_CloseDevice.
800 */
801 
PortBurn_GetStatus(void * handle,float * out_fraction_complete)802 int PortBurn_GetStatus(void *handle, float *out_fraction_complete)
803 {
804    PBHandle *h = (PBHandle *)handle;
805    CFDictionaryRef status;
806    CFStringRef stateRef;
807    CFNumberRef fracRef;
808    CFNumberRef trackNumRef;
809    float frac = 0.0;
810    int trackNum = 0;
811 
812    *out_fraction_complete = h->frac;
813 
814    if (h == NULL) {
815       return pbErrNoHandle;
816    }
817 
818    if (h->burn == NULL) {
819       return pbErrNotCurrentlyBurning;
820    }
821 
822    status = DRBurnCopyStatus(h->burn);
823    if (status == NULL) {
824       return pbErrCannotGetBurnStatus;
825    }
826 
827    trackNumRef = (CFNumberRef) CFDictionaryGetValue(status, kDRStatusCurrentTrackKey);
828    if (trackNumRef != NULL) {
829       CFNumberGetValue(trackNumRef, kCFNumberIntType, &trackNum);
830    }
831 
832    fracRef = (CFNumberRef) CFDictionaryGetValue(status, kDRStatusPercentCompleteKey);
833    if (fracRef != NULL) {
834       CFNumberGetValue(fracRef, kCFNumberFloatType, &frac);
835    }
836 
837    stateRef = (CFStringRef) CFDictionaryGetValue(status, kDRStatusStateKey);
838 
839    if (stateRef == NULL) {
840       /* Stick with the last percentage */
841       return pbSuccess;
842    }
843 
844    if (CFEqual(stateRef, kDRStatusStateNone)) {
845       /* Stick with the last percentage */
846       return pbSuccess;
847    }
848 
849    if (CFEqual(stateRef, kDRStatusStatePreparing)) {
850       /* This takes about 1 second */
851       h->frac = 0.01;
852    }
853 
854    if (CFEqual(stateRef, kDRStatusStateSessionOpen)) {
855       /* This takes about 3 seconds */
856       h->frac = 0.02;
857    }
858 
859    if (CFEqual(stateRef, kDRStatusStateTrackOpen) ||
860        CFEqual(stateRef, kDRStatusStateTrackWrite) ||
861        CFEqual(stateRef, kDRStatusStateTrackClose) ||
862        CFEqual(stateRef, kDRStatusStateSessionClose)) {
863       /* The fraction ("percentage") complete will be valid during
864          the majority of this range */
865       float newFrac = 0.0;
866       if (frac > 0.0 && frac <= 1.0) {
867          newFrac = frac;
868       }
869 
870       /* Scale it to the range 0.05 - 0.99 */
871       newFrac = 0.05 + 0.94 * newFrac;
872 
873       /* Only use that value if it's larger than the previous value */
874       if (newFrac > h->frac) {
875          h->frac = newFrac;
876       }
877    }
878 
879    if (CFEqual(stateRef, kDRStatusStateDone)) {
880       /* Returning a fraction complete of 1.0 means we're done! */
881       h->frac = 1.0;
882       CFRelease(h->burn);
883       h->burn = NULL;
884    }
885 
886    if (CFEqual(stateRef, kDRStatusStateFailed)) {
887       CFRelease(status);
888       return pbErrBurnFailed;
889    }
890 
891    *out_fraction_complete = h->frac;
892 
893    CFRelease(status);
894 
895    return pbSuccess;
896 }
897 
898 /* Get option value. */
899 
PortBurn_GetOption(void * handle,int option,int * value)900 int PortBurn_GetOption(void *handle, int option, int *value)
901 {
902    PBHandle *h = (PBHandle *)handle;
903    int ret = pbSuccess;
904 
905    if (h == NULL) {
906       return pbErrNoHandle;
907    }
908 
909    switch (option)
910    {
911       case pbOptTest:
912       {
913          *value = h->test;
914       }
915       break;
916 
917       case pbOptVerify:
918       {
919          *value = h->verify;
920       }
921       break;
922 
923       case pbOptUnderrun:
924       {
925          *value = h->underrun;
926       }
927       break;
928 
929       case pbOptEject:
930       {
931          *value = h->eject;
932       }
933       break;
934 
935       case pbOptGapless:
936       {
937          *value = h->gapless;
938       }
939       break;
940 
941       case pbOptSpeed:
942       {
943       }
944       break;
945 
946       default:
947       {
948          ret = pbErrInvalidOption;
949       }
950       break;
951    }
952 
953    return ret;
954 }
955 
PortBurn_SetOption(void * handle,int option,int value)956 int PortBurn_SetOption(void *handle, int option, int value)
957 {
958    PBHandle *h = (PBHandle *)handle;
959    int ret = pbSuccess;
960 
961    if (h == NULL) {
962       return pbErrNoHandle;
963    }
964 
965    switch (option)
966    {
967       case pbOptTest:
968       {
969          h->test = value != 0;
970       }
971       break;
972 
973       case pbOptVerify:
974       {
975          h->verify = value != 0;
976       }
977       break;
978 
979       case pbOptUnderrun:
980       {
981          h->underrun = value != 0;
982       }
983       break;
984 
985       case pbOptEject:
986       {
987          h->eject = value != 0;
988       }
989       break;
990 
991       case pbOptGapless:
992       {
993          h->gapless = value;
994       }
995       break;
996 
997       case pbOptSpeed:
998       {
999          h->speed = value;
1000       }
1001       break;
1002 
1003       default:
1004       {
1005          ret = pbErrInvalidOption;
1006       }
1007       break;
1008    }
1009 
1010    return ret;
1011 }
1012 
PortBurn_GetSpeeds(void * handle,int * cnt,int * speeds[])1013 int PortBurn_GetSpeeds(void *handle, int *cnt, int *speeds[])
1014 {
1015    PBHandle *h = (PBHandle *)handle;
1016    CFDictionaryRef deviceStatus;
1017    CFDictionaryRef mediaInfo;
1018    CFStringRef mediaState;
1019 
1020    if (h == NULL) {
1021       return pbErrNoHandle;
1022    }
1023 
1024    if (h->device == NULL) {
1025       return pbErrDeviceNotOpen;
1026    }
1027 
1028    /* First we check to see that we have blank media in the drive. */
1029    deviceStatus = DRDeviceCopyStatus(h->device);
1030    if (deviceStatus == NULL) {
1031       return pbErrCannotAccessDevice;
1032    }
1033 
1034    mediaState = (CFStringRef) CFDictionaryGetValue(deviceStatus,
1035                                                    kDRDeviceMediaStateKey);
1036    if (mediaState == NULL) {
1037       CFRelease(deviceStatus);
1038       return pbErrCannotAccessDevice;
1039    }
1040 
1041    if (!CFEqual(mediaState, kDRDeviceMediaStateMediaPresent)) {
1042       CFRelease(deviceStatus);
1043       return pbErrNoMediaInDrive;
1044    }
1045 
1046    mediaInfo = (CFDictionaryRef) CFDictionaryGetValue(deviceStatus,
1047                                                       kDRDeviceMediaInfoKey);
1048 
1049    CFArrayRef sarray = (CFArrayRef)
1050       CFDictionaryGetValue(mediaInfo, kDRDeviceBurnSpeedsKey);
1051 
1052    if (sarray != NULL && CFArrayGetCount(sarray) != 0) {
1053       CFIndex ndx;
1054       *cnt = CFArrayGetCount(sarray);
1055       *speeds = (int *) malloc((*cnt + 1) * sizeof(int));
1056       for (ndx = 0; ndx < *cnt; ndx++) {
1057          float speed;
1058 
1059          CFNumberRef num = (CFNumberRef)
1060             CFArrayGetValueAtIndex(sarray, ndx);
1061 
1062          if (num != NULL) {
1063             CFNumberGetValue(num, kCFNumberFloatType, &speed);
1064             *speeds[ndx] = lrint(speed / kDRDeviceBurnSpeedCD1x);
1065          }
1066       }
1067    }
1068    CFRelease(deviceStatus);
1069 
1070    return pbSuccess;
1071 }
1072