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