/* vim: set ft=objc et sw=4 ts=4 nowrap: */ /* * CDrecordController.m * * Copyright (c) 2002-2004 * * Author: Andreas Heppel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include "CDrecordController.h" #include "Constants.h" #include "Functions.h" #include "Track.h" #ifdef _ #undef _ #endif #define _(X) \ [[NSBundle bundleForClass: [self class]] localizedStringForKey:(X) value:@"" table:nil] @implementation CDrecordController (Private) /** *

checkForDrives runs cdrecord to get the list * of available drives. For performance reasons this should be done * only once when the bundle is loaded. */ NSString *transports[] = { @"", @"ATAPI" }; - (void) checkForDrives { NSString* cdrecord; int count, i, j; NSDictionary *parameters = [[NSUserDefaults standardUserDefaults] objectForKey: @"CDrecordParameters"]; cdrecord = [parameters objectForKey: @"Program"]; /* * It may be that the path to cdrecord is not set, yet. This * is the case, when Burn is run for the first time. In this case * we cannot do anything here. */ if (!checkProgram(cdrecord)) return; [[NSNotificationCenter defaultCenter] postNotificationName: DisplayWorkInProgress object: nil userInfo: [NSDictionary dictionaryWithObjectsAndKeys: @"1", @"Start", @"cdrecord", @"AppName", @"Checking for drives. Please wait.", @"DisplayString", nil]]; RELEASE(drives); drives = [NSMutableDictionary new]; /* * Walk the list of transport layers and for each call the scanbus command. * Then concatenate the output lines to one array. */ for (i = 0; i < sizeof(transports)/sizeof(NSString*); i++) { NSPipe *stdOut = [[NSPipe alloc] init]; NSMutableArray* cdrArgs = [[NSMutableArray alloc] init]; NSArray *output; if ([transports[i] length]) [cdrArgs addObject: [NSString stringWithFormat: @"dev=%@:", transports[i]]]; [cdrArgs addObject: [NSString stringWithFormat: @"-scanbus"]]; cdrTask = [[NSTask alloc] init]; [cdrTask setLaunchPath: cdrecord]; [cdrTask setArguments: cdrArgs]; [cdrTask setStandardOutput: stdOut]; [cdrTask setStandardError: stdOut]; [cdrTask launch]; [cdrTask waitUntilExit]; output = [[[NSString alloc] initWithData: [[stdOut fileHandleForReading] availableData] encoding: NSISOLatin1StringEncoding] componentsSeparatedByString: @"\n"]; count = [output count]; for (j = 0; j < count; j++) { NSString *line; NSString *vendorString, *modelString, *revString; NSString *busIdLunString; line = [[output objectAtIndex: j] stringByTrimmingLeadSpaces]; /* * Check whether it is a CD-ROM, first. */ if (([line rangeOfString: @"CD-ROM"].location != NSNotFound)) { /* triple of bus, id, lun comes first */ busIdLunString = [line substringToIndex: [line rangeOfString: @"\t"].location]; line = [line substringFromIndex: [line rangeOfString: @"'"].location+1]; /* Vendor, Model, Revision are encapsulated by ' */ vendorString = [[line substringToIndex: [line rangeOfString: @"\'"].location] stringByTrimmingSpaces]; line = [line substringFromIndex: [line rangeOfString: @"' '"].location+3]; modelString = [[line substringToIndex: [line rangeOfString: @"\'"].location] stringByTrimmingSpaces]; line = [line substringFromIndex: [line rangeOfString: @"' '"].location+3]; revString = [[line substringToIndex: [line rangeOfString: @"\'"].location] stringByTrimmingSpaces]; if ([transports[i] length]) [drives setObject: transports[i] forKey:[NSString stringWithFormat: @"%@: %@ %@ %@", busIdLunString, vendorString, modelString, revString]]; else [drives setObject: @"" forKey: [NSString stringWithFormat: @"%@: %@ %@ %@", busIdLunString, vendorString, modelString, revString]]; } } } [[NSNotificationCenter defaultCenter] postNotificationName: DisplayWorkInProgress object: nil userInfo: [NSDictionary dictionaryWithObjectsAndKeys: @"0", @"Start", @"cdrecord", @"AppName", nil]]; } /** *

getCDrecordDrivers starts cdrecord with the * parameter driver=help to retrieve a list of all * built-in drivers.

*

No new thread is started as this takes only a very short amount * of time, and the caller must wait for the result anyway.

*/ - (void) getCDrecordDrivers { int termStatus; NSString *cdrecord; NSPipe *cdrStdout; NSFileHandle *hdl; NSArray *cdrOutput; NSString *outLine; NSArray *cdrArgs; int count, i; NSDictionary *parameters = [[NSUserDefaults standardUserDefaults] objectForKey: @"CDrecordParameters"]; [drivers removeAllObjects]; [drivers addObject: @"Default"]; cdrecord = [parameters objectForKey: @"Program"]; if (!checkProgram(cdrecord)) return; cdrStdout = [[NSPipe alloc] init]; cdrTask = [[NSTask alloc] init]; cdrArgs = [NSArray arrayWithObjects: [NSString stringWithString: @"driver=help"], nil]; [cdrTask setLaunchPath: cdrecord]; [cdrTask setArguments: cdrArgs]; [cdrTask setStandardError: cdrStdout]; [cdrTask launch]; [cdrTask waitUntilExit]; // FreeBSD needs an lvalue for the WEXITSTATUS macros termStatus = [cdrTask terminationStatus]; if (WEXITSTATUS(termStatus) == 0) { hdl = [cdrStdout fileHandleForReading]; cdrOutput = [[[NSString alloc] initWithData: [hdl availableData] encoding: NSISOLatin1StringEncoding] componentsSeparatedByString: @"\n"]; count = [cdrOutput count]; /* * Skip the first line in output. It contains only header data. */ for (i = 1; i < count; i++) { NSRange range; outLine = [cdrOutput objectAtIndex: i]; range = [outLine rangeOfCharacterFromSet: [NSCharacterSet whitespaceCharacterSet]]; if (range.location != NSNotFound) { [drivers addObject: [outLine substringToIndex: range.location]]; } } } RELEASE(cdrTask); RELEASE(cdrStdout); } /** *

waitForEndOfBurning waits until the current burning process * is finished.

*

The method reads the output from cdrecord * and processes it. This means that waitForEndOfBurning updates * the different progress values (entire progress, track progress,...) * and sends cdrecord's output to the console.

*/ - (void) waitForEndOfBurning { BOOL sendLine; int actCDProgress; int maxCDProgress; int maxTrackProgress, curTrackProgress; maxCDProgress = 0; actCDProgress = 0; maxTrackProgress = 0; curTrackProgress = 0; while ([cdrTask isRunning]) { NSData *inData; while ((inData = [[[cdrTask standardError] fileHandleForReading] availableData]) && [inData length]) { int i, count; NSString *aLine; NSArray *theOutput; theOutput = [[[NSString alloc] initWithData: inData encoding: NSISOLatin1StringEncoding] componentsSeparatedByString: @"\n"]; count = [theOutput count]; for (i = 0; i < count; i++) { aLine = [theOutput objectAtIndex: i]; if (aLine && [aLine length]) sendLine = YES; else sendLine = NO; // don't send empty lines if (burnStatus.processStatus == isWaiting) { NSRange aRange = [aLine rangeOfString: @"No disk / Wrong disk!"]; if (aRange.location != NSNotFound) { [statusLock lock]; burnStatus.processStatus = isCancelled; [statusLock unlock]; break; } aRange = [aLine rangeOfString: @"Total size"]; if (aRange.location != NSNotFound) { maxCDProgress = [[aLine substringFromIndex: 16] intValue]; } aRange = [aLine rangeOfString: @"Operation starts."]; if (aRange.location != NSNotFound) { [statusLock lock]; burnStatus.processStatus = isPreparing; [statusLock unlock]; } } else if ((burnStatus.processStatus == isPreparing) || (burnStatus.processStatus == isBurning)) { NSRange aRange = [aLine rangeOfString: @"Starting new track"]; if (aRange.location != NSNotFound) { [statusLock lock]; actCDProgress += curTrackProgress; burnStatus.trackNumber++; burnStatus.trackProgress = 0.; curTrackProgress = 0; maxTrackProgress = (long)[[burnTracks objectAtIndex: burnStatus.trackNumber-1] duration] * 2352 / (1024*1024); [statusLock unlock]; sendLine = NO; } aRange = [aLine rangeOfString: @"MB written (fifo"]; if (aRange.location != NSNotFound) { [statusLock lock]; if (burnStatus.processStatus == isPreparing) burnStatus.processStatus = isBurning; curTrackProgress = [[aLine substringWithRange: NSMakeRange(aRange.location-12,4)] intValue]; burnStatus.trackProgress = curTrackProgress*100/maxTrackProgress; burnStatus.bufferLevel = [[aLine substringWithRange: NSMakeRange(aRange.location+16,4)] doubleValue]; burnStatus.entireProgress = (actCDProgress+curTrackProgress) * 100 / maxCDProgress; [statusLock unlock]; sendLine = NO; } else { aRange = [aLine rangeOfString: @"Fixating"]; if (aRange.location != NSNotFound) { [statusLock lock]; burnStatus.processStatus = isFixating; burnStatus.trackProgress = 0.; burnStatus.bufferLevel = 0.; burnStatus.entireProgress = 0.; [statusLock unlock]; } } } // post the oputput to the progress panel if (sendLine) { [self sendOutputString: aLine raw: YES]; } } // for (i = 0; i < count; i++) } // while ((inData = } // while ([cdrTask isRunning]) } - (NSMutableArray *) makeParamsForTask: (CDrecordTask) task withParameters: (NSDictionary *) parameters { NSRange range = NSMakeRange(NSNotFound, 0); NSString *burnDevice; NSString *transport; NSString *nextParam; NSMutableArray *cdrArgs; NSDictionary *availDrivers; NSDictionary *sessionParams = [parameters objectForKey: @"SessionParameters"]; NSDictionary *cdrParams = [parameters objectForKey: @"CDrecordParameters"]; NSDictionary *tools = [parameters objectForKey: @"SelectedTools"]; /* * Retrieve the selected burner device from the params and then get * the list of assigned drivers for this device. */ burnDevice = [tools objectForKey: BurnDevice]; transport = [drives objectForKey: burnDevice]; availDrivers = [[parameters objectForKey: Drivers] objectForKey: burnDevice]; /* * Now extract the SCSI device ID from the device string. * The format of the string is: "X,Y,Z: Device Name" */ if (burnDevice && [burnDevice length]) { range = [burnDevice rangeOfString: @": "]; } if (range.location == NSNotFound) { [self sendOutputString: [NSString stringWithFormat: @"%@\n%@", _(@"No burning device specified."), _(@"Process will be stopped.")] raw: NO]; [NSException raise: NSInternalInconsistencyException format: @"cdrecord cannot find a burning device."]; } burnDevice = [burnDevice substringToIndex: range.location]; /* The array is autoreleased! Don't release it here!!! */ cdrArgs = [NSMutableArray arrayWithObjects: @"-v", nil]; // set device if (transport && [transport length]) { [cdrArgs addObject: [NSString stringWithFormat: @"dev=%@:%@", transport, burnDevice]]; } else { [cdrArgs addObject: [NSString stringWithFormat: @"dev=%@", burnDevice]]; } // we don't need the next stuff to simply eject the disk if (task != TaskEject) { // set driver and options nextParam = [availDrivers objectForKey: [self name]]; if (nextParam && [nextParam length] && ![nextParam isEqual: @"Default"]) { [cdrArgs addObject: [NSString stringWithFormat: @"driver=%@", nextParam]]; } nextParam = [cdrParams objectForKey: DriverOptions]; if (nextParam && [nextParam length]) { [cdrArgs addObject: [NSString stringWithFormat: @"driveropts=%@", nextParam]]; } } // make param string for burn command switch (task) { case TaskBurn: // set speed nextParam = [sessionParams objectForKey: BurnSpeed]; if (nextParam && [nextParam length]) { [cdrArgs addObject: [NSString stringWithFormat: @"-speed=%@", nextParam]]; } else { [cdrArgs addObject: [NSString stringWithFormat: @"-speed=0"]]; } if ([[cdrParams objectForKey: @"TrackAtOnce"] boolValue] == NO) { [cdrArgs addObject: @"-dao"]; } if ([[sessionParams objectForKey: TestOnly] intValue]) { [cdrArgs addObject: @"-dummy"]; } if ([[sessionParams objectForKey: EjectCD] boolValue] == YES) { [cdrArgs addObject: @"-eject"]; } if ([[sessionParams objectForKey: Overburn] boolValue] == YES) { [cdrArgs addObject: @"-overburn"]; } break; case TaskEject: // eject media [cdrArgs addObject: @"-eject"]; [cdrArgs addObject: @"-dummy"]; break; case TaskBlank: [cdrArgs addObject: @"-eject"]; break; case TaskMediaInfo: [cdrArgs addObject: @"-atip"]; break; } return cdrArgs; } - (void) appendTrackArgs: (NSMutableArray *)args forCDROM: (BOOL)isCDROM { int i, count; Track *track; count = [burnTracks count]; if (isCDROM) { [args addObject: @"-data"]; track = [burnTracks objectAtIndex: 0]; [args addObject: [track storage]]; if (count > 1) { [args addObject: @"-audio"]; [args addObject: @"-pad"]; } } else { [args addObject: @"-audio"]; [args addObject: @"-pad"]; } i = isCDROM ? 1 : 0; for (; i < count; i++) { track = [burnTracks objectAtIndex: i]; NSString *storage = [track storage]; [args addObject: storage]; } } - (void) sendOutputString: (NSString *)outString raw: (BOOL)raw { NSString *outLine; if (raw == NO) outLine = [NSString stringWithFormat: @"**** %@ ****", outString]; else outLine = outString; [[NSDistributedNotificationCenter defaultCenter] postNotificationName: ExternalToolOutput object: nil userInfo: [NSDictionary dictionaryWithObject: outLine forKey: Output]]; } @end