/* * * $Id: k3bgrowisofswriter.cpp 731898 2007-11-02 08:22:18Z trueg $ * Copyright (C) 2003 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * 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. * See the file "COPYING" for the exact licensing terms. */ #include "k3bgrowisofswriter.h" #include #include #include #include #include #include #include #include #include #include "k3bgrowisofshandler.h" #include #include #include #include #include #include #include #include #include class K3bGrowisofsWriter::Private { public: Private() : writingMode( 0 ), closeDvd(false), multiSession(false), process( 0 ), growisofsBin( 0 ), trackSize(-1), layerBreak(0), usingRingBuffer(false), ringBuffer(0), forceNoEject( false ) { } int writingMode; bool closeDvd; bool multiSession; K3bProcess* process; const K3bExternalBin* growisofsBin; TQString image; bool success; bool canceled; bool finished; TQTime lastSpeedCalculationTime; int lastSpeedCalculationBytes; int lastProgress; unsigned int lastProgressed; double lastWritingSpeed; bool writingStarted; K3bThroughputEstimator* speedEst; K3bGrowisofsHandler* gh; // used in DAO with growisofs >= 5.15 long trackSize; long layerBreak; unsigned long long overallSizeFromOutput; long long firstSizeFromOutput; TQFile inputFile; bool usingRingBuffer; K3bPipeBuffer* ringBuffer; TQString multiSessionInfo; bool forceNoEject; }; K3bGrowisofsWriter::K3bGrowisofsWriter( K3bDevice::Device* dev, K3bJobHandler* hdl, TQObject* parent, const char* name ) : K3bAbstractWriter( dev, hdl, parent, name ) { d = new Private; d->speedEst = new K3bThroughputEstimator( this ); connect( d->speedEst, TQT_SIGNAL(throughput(int)), this, TQT_SLOT(slotThroughput(int)) ); d->gh = new K3bGrowisofsHandler( this ); connect( d->gh, TQT_SIGNAL(infoMessage(const TQString&, int)), this,TQT_SIGNAL(infoMessage(const TQString&, int)) ); connect( d->gh, TQT_SIGNAL(newSubTask(const TQString&)), this, TQT_SIGNAL(newSubTask(const TQString&)) ); connect( d->gh, TQT_SIGNAL(buffer(int)), this, TQT_SIGNAL(buffer(int)) ); connect( d->gh, TQT_SIGNAL(deviceBuffer(int)), this, TQT_SIGNAL(deviceBuffer(int)) ); connect( d->gh, TQT_SIGNAL(flushingCache()), this, TQT_SLOT(slotFlushingCache()) ); } K3bGrowisofsWriter::~K3bGrowisofsWriter() { delete d->process; delete d; } bool K3bGrowisofsWriter::active() const { return (d->process ? d->process->isRunning() : false); } int K3bGrowisofsWriter::fd() const { if( d->process ) { if( d->usingRingBuffer ) return d->ringBuffer->inFd(); else return d->process->stdinFd(); } else return -1; } bool K3bGrowisofsWriter::closeFd() { return ( !::close( fd() ) ); } bool K3bGrowisofsWriter::prepareProcess() { d->growisofsBin = k3bcore->externalBinManager()->binObject( "growisofs" ); if( !d->growisofsBin ) { emit infoMessage( i18n("Could not find %1 executable.").arg("growisofs"), ERROR ); return false; } if( d->growisofsBin->version < K3bVersion( 5, 10 ) ) { emit infoMessage( i18n("Growisofs version %1 is too old. " "K3b needs at least version 5.10.").arg(d->growisofsBin->version), ERROR ); return false; } emit debuggingOutput( "Used versions", "growisofs: " + d->growisofsBin->version ); if( !d->growisofsBin->copyright.isEmpty() ) emit infoMessage( i18n("Using %1 %2 - Copyright (C) %3").arg("growisofs") .arg(d->growisofsBin->version).arg(d->growisofsBin->copyright), INFO ); // // The growisofs bin is ready. Now we add the parameters // delete d->process; d->process = new K3bProcess(); d->process->setRunPrivileged(true); // d->process->setPriority( KProcess::PrioHighest ); d->process->setSplitStdout(true); d->process->setRawStdin(true); connect( d->process, TQT_SIGNAL(stderrLine(const TQString&)), this, TQT_SLOT(slotReceivedStderr(const TQString&)) ); connect( d->process, TQT_SIGNAL(stdoutLine(const TQString&)), this, TQT_SLOT(slotReceivedStderr(const TQString&)) ); connect( d->process, TQT_SIGNAL(processExited(KProcess*)), this, TQT_SLOT(slotProcessExited(KProcess*)) ); // // growisofs < 5.20 wants the tracksize to be a multiple of 16 (1 ECC block: 16*2048 bytes) // we simply pad ourselves. // // But since the writer itself properly pads or writes a longer lead-out we don't really need // to write zeros. We just tell growisofs to reserve a multiple of 16 blocks. // This is only releveant in DAO mode anyway. // // FIXME: seems as we also need this for double layer writing. Better make it the default and // actually write the pad bytes. The only possibility I see right now is to add a padding option // to the pipebuffer. int trackSizePadding = 0; if( d->trackSize > 0 && d->growisofsBin->version < K3bVersion( 5, 20 ) ) { if( d->trackSize % 16 ) { trackSizePadding = (16 - d->trackSize%16); kdDebug() << "(K3bGrowisofsWriter) need to pad " << trackSizePadding << " blocks." << endl; } } *d->process << d->growisofsBin; // set this var to true to enable the ringbuffer d->usingRingBuffer = ( d->growisofsBin->version < K3bVersion( 6, 0 ) ); TQString s = burnDevice()->blockDeviceName() + "="; if( d->usingRingBuffer || d->image.isEmpty() ) { // we always read from stdin since the ringbuffer does the actual reading from the source s += "/dev/fd/0"; } else s += d->image; if( d->multiSession && !d->multiSessionInfo.isEmpty() ) *d->process << "-C" << d->multiSessionInfo; if( d->multiSession ) *d->process << "-M"; else *d->process << "-Z"; *d->process << s; if( !d->image.isEmpty() && d->usingRingBuffer ) { d->inputFile.setName( d->image ); d->trackSize = (K3b::filesize( d->image ) + 1024) / 2048; if( !d->inputFile.open( IO_ReadOnly ) ) { emit infoMessage( i18n("Could not open file %1.").arg(d->image), ERROR ); return false; } } // now we use the force (luke ;) do not reload the dvd, K3b does that. *d->process << "-use-the-force-luke=notray"; // we check for existing filesystems ourselves, so we always force the overwrite... *d->process << "-use-the-force-luke=tty"; bool dvdCompat = d->closeDvd; // DL writing with forced layer break if( d->layerBreak > 0 ) { *d->process << "-use-the-force-luke=break:" + TQString::number(d->layerBreak); dvdCompat = true; } // the tracksize parameter takes priority over the dao:tracksize parameter since growisofs 5.18 else if( d->growisofsBin->version > K3bVersion( 5, 17 ) && d->trackSize > 0 ) *d->process << "-use-the-force-luke=tracksize:" + TQString::number(d->trackSize + trackSizePadding); if( simulate() ) *d->process << "-use-the-force-luke=dummy"; if( d->writingMode == K3b::DAO ) { dvdCompat = true; if( d->growisofsBin->version >= K3bVersion( 5, 15 ) && d->trackSize > 0 ) *d->process << "-use-the-force-luke=dao:" + TQString::number(d->trackSize + trackSizePadding); else *d->process << "-use-the-force-luke=dao"; d->gh->reset( burnDevice(), true ); } else d->gh->reset( burnDevice(), false ); // // Never use the -dvd-compat parameter with DVD+RW media // because the only thing it does is creating problems. // Normally this should be done in growisofs // int mediaType = burnDevice()->mediaType(); if( dvdCompat && mediaType != K3bDevice::MEDIA_DVD_PLUS_RW && mediaType != K3bDevice::MEDIA_DVD_RW_OVWR ) *d->process << "-dvd-compat"; // // Some DVD writers do not allow changing the writing speed so we allow // the user to ignore the speed setting // int speed = burnSpeed(); if( speed >= 0 ) { if( speed == 0 ) { // try to determine the writeSpeed // if it fails determineOptimalWriteSpeed() will return 0 and // the choice is left to growisofs which means that the choice is // really left to the drive since growisofs does not change the speed // if no option is given speed = burnDevice()->determineMaximalWriteSpeed(); } // speed may be a float number. example: DVD+R(W): 2.4x if( speed != 0 ) *d->process << TQString("-speed=%1").arg( speed%1385 > 0 ? TQString::number( (float)speed/1385.0, 'f', 1 ) : TQString::number( speed/1385 ) ); } if( k3bcore->globalSettings()->overburn() ) *d->process << "-overburn"; if( !d->usingRingBuffer && d->growisofsBin->version >= K3bVersion( 6, 0 ) ) { bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 32 ); *d->process << TQString("-use-the-force-luke=bufsize:%1m").arg(bufSize); } // additional user parameters from config const TQStringList& params = d->growisofsBin->userParameters(); for( TQStringList::const_iterator it = params.begin(); it != params.end(); ++it ) *d->process << *it; emit debuggingOutput( "Burned media", K3bDevice::mediaTypeString(mediaType) ); return true; } void K3bGrowisofsWriter::start() { jobStarted(); d->lastWritingSpeed = 0; d->lastProgressed = 0; d->lastProgress = 0; d->firstSizeFromOutput = -1; d->lastSpeedCalculationTime = TQTime::currentTime(); d->lastSpeedCalculationBytes = 0; d->writingStarted = false; d->canceled = false; d->speedEst->reset(); d->finished = false; if( !prepareProcess() ) { jobFinished( false ); } else { kdDebug() << "***** " << d->growisofsBin->name() << " parameters:\n"; const TQValueList& args = d->process->args(); TQString s; for( TQValueList::const_iterator it = args.begin(); it != args.end(); ++it ) { s += *it + " "; } kdDebug() << s << flush << endl; emit debuggingOutput( d->growisofsBin->name() + " command:", s); emit newSubTask( i18n("Preparing write process...") ); // FIXME: check the return value if( K3b::isMounted( burnDevice() ) ) { emit infoMessage( i18n("Unmounting medium"), INFO ); K3b::unmount( burnDevice() ); } // block the device (including certain checks) k3bcore->blockDevice( burnDevice() ); // lock the device for good in this process since it will // be opened in the growisofs process burnDevice()->close(); burnDevice()->usageLock(); if( !d->process->start( KProcess::NotifyOnExit, KProcess::All ) ) { // something went wrong when starting the program // it "should" be the executable kdDebug() << "(K3bGrowisofsWriter) could not start " << d->growisofsBin->path << endl; emit infoMessage( i18n("Could not start %1.").arg(d->growisofsBin->name()), K3bJob::ERROR ); jobFinished(false); } else { if( simulate() ) { emit newTask( i18n("Simulating") ); emit infoMessage( i18n("Starting simulation..."), K3bJob::INFO ); } else { emit newTask( i18n("Writing") ); emit infoMessage( i18n("Starting disc write..."), K3bJob::INFO ); } d->gh->handleStart(); // create the ring buffer if( d->usingRingBuffer ) { if( !d->ringBuffer ) { d->ringBuffer = new K3bPipeBuffer( this, this ); connect( d->ringBuffer, TQT_SIGNAL(percent(int)), this, TQT_SIGNAL(buffer(int)) ); connect( d->ringBuffer, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotRingBufferFinished(bool)) ); } d->ringBuffer->writeToFd( d->process->stdinFd() ); bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize(); int bufSize = ( manualBufferSize ? k3bcore->globalSettings()->bufferSize() : 20 ); d->ringBuffer->setBufferSize( bufSize ); if( !d->image.isEmpty() ) d->ringBuffer->readFromFd( d->inputFile.handle() ); d->ringBuffer->start(); } } } } void K3bGrowisofsWriter::cancel() { if( active() ) { d->canceled = true; closeFd(); if( d->usingRingBuffer && d->ringBuffer ) d->ringBuffer->cancel(); d->process->kill(); } } void K3bGrowisofsWriter::setWritingMode( int m ) { d->writingMode = m; } void K3bGrowisofsWriter::setTrackSize( long size ) { d->trackSize = size; } void K3bGrowisofsWriter::setLayerBreak( long lb ) { d->layerBreak = lb; } void K3bGrowisofsWriter::setCloseDvd( bool b ) { d->closeDvd = b; } void K3bGrowisofsWriter::setMultiSession( bool b ) { d->multiSession = b; } void K3bGrowisofsWriter::setImageToWrite( const TQString& filename ) { d->image = filename; } void K3bGrowisofsWriter::slotReceivedStderr( const TQString& line ) { emit debuggingOutput( d->growisofsBin->name(), line ); if( line.contains( "remaining" ) ) { if( !d->writingStarted ) { d->writingStarted = true; emit newSubTask( i18n("Writing data") ); } // parse progress int pos = line.find( "/" ); unsigned long long done = line.left( pos ).toULongLong(); bool ok = true; d->overallSizeFromOutput = line.mid( pos+1, line.find( "(", pos ) - pos - 1 ).toULongLong( &ok ); if( d->firstSizeFromOutput == -1 ) d->firstSizeFromOutput = done; done -= d->firstSizeFromOutput; d->overallSizeFromOutput -= d->firstSizeFromOutput; if( ok ) { int p = (int)(100 * done / d->overallSizeFromOutput); if( p > d->lastProgress ) { emit percent( p ); emit subPercent( p ); d->lastProgress = p; } if( (unsigned int)(done/1024/1024) > d->lastProgressed ) { d->lastProgressed = (unsigned int)(done/1024/1024); emit processedSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024) ); emit processedSubSize( d->lastProgressed, (int)(d->overallSizeFromOutput/1024/1024) ); } // try parsing write speed (since growisofs 5.11) pos = line.find( '@' ); if( pos != -1 ) { pos += 1; double speed = line.mid( pos, line.find( 'x', pos ) - pos ).toDouble(&ok); if( ok ) { if( d->lastWritingSpeed != speed ) emit writeSpeed( (int)(speed*1385.0), 1385 ); d->lastWritingSpeed = speed; } else kdDebug() << "(K3bGrowisofsWriter) speed parsing failed: '" << line.mid( pos, line.find( 'x', pos ) - pos ) << "'" << endl; } else { d->speedEst->dataWritten( done/1024 ); } } else kdDebug() << "(K3bGrowisofsWriter) progress parsing failed: '" << line.mid( pos+1, line.find( "(", pos ) - pos - 1 ).stripWhiteSpace() << "'" << endl; } // else // to be able to parse the ring buffer fill in growisofs 6.0 we need to do this all the time // FIXME: get rid of the K3bGrowisofsHandler once it is sure that we do not need the K3bGrowisofsImager anymore d->gh->handleLine( line ); } void K3bGrowisofsWriter::slotProcessExited( KProcess* p ) { d->inputFile.close(); // release the device within this process burnDevice()->usageUnlock(); // unblock the device k3bcore->unblockDevice( burnDevice() ); if( d->canceled ) { if( !d->finished ) { d->finished = true; // this will unblock and eject the drive and emit the finished/canceled signals K3bAbstractWriter::cancel(); } return; } d->finished = true; // it seems that growisofs sometimes exits with a valid exit code while a write error occured if( p->exitStatus() == 0 && d->gh->error() != K3bGrowisofsHandler::ERROR_WRITE_FAILED ) { int s = d->speedEst->average(); if( s > 0 ) emit infoMessage( i18n("Average overall write speed: %1 KB/s (%2x)") .arg(s).arg(KGlobal::locale()->formatNumber((double)s/1385.0), 2), INFO ); if( simulate() ) emit infoMessage( i18n("Simulation successfully completed"), K3bJob::SUCCESS ); else emit infoMessage( i18n("Writing successfully completed"), K3bJob::SUCCESS ); d->success = true; } else { if( !wasSourceUnreadable() ) d->gh->handleExit( p->exitStatus() ); d->success = false; } if( !k3bcore->globalSettings()->ejectMedia() || d->forceNoEject ) jobFinished(d->success); else { emit newSubTask( i18n("Ejecting DVD") ); connect( K3bDevice::eject( burnDevice() ), TQT_SIGNAL(finished(K3bDevice::DeviceHandler*)), this, TQT_SLOT(slotEjectingFinished(K3bDevice::DeviceHandler*)) ); } } void K3bGrowisofsWriter::slotRingBufferFinished( bool ) { if( !d->finished ) { d->finished = true; // this will unblock and eject the drive and emit the finished/canceled signals K3bAbstractWriter::cancel(); } } void K3bGrowisofsWriter::slotEjectingFinished( K3bDevice::DeviceHandler* dh ) { if( !dh->success() ) emit infoMessage( i18n("Unable to eject media."), ERROR ); jobFinished(d->success); } void K3bGrowisofsWriter::slotThroughput( int t ) { emit writeSpeed( t, 1385 ); } void K3bGrowisofsWriter::slotFlushingCache() { if( !d->canceled ) { // // growisofs's progress output stops before 100%, so we do it manually // emit percent( 100 ); emit processedSize( d->overallSizeFromOutput/1024/1024, d->overallSizeFromOutput/1024/1024 ); } } void K3bGrowisofsWriter::setMultiSessionInfo( const TQString& info ) { d->multiSessionInfo = info; } void K3bGrowisofsWriter::setForceNoEject( bool b ) { d->forceNoEject = b; } #include "k3bgrowisofswriter.moc"