You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
k3b/libk3b/projects/k3bgrowisofswriter.cpp

631 lines
18 KiB

/*
*
* $Id: k3bgrowisofswriter.cpp 731898 2007-11-02 08:22:18Z trueg $
* Copyright (C) 2003 Sebastian Trueg <trueg@k3b.org>
*
* This file is part of the K3b project.
* Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
*
* 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 <k3bcore.h>
#include <k3bdevice.h>
#include <k3bdevicehandler.h>
#include <k3bprocess.h>
#include <k3bexternalbinmanager.h>
#include <k3bversion.h>
#include <k3bdiskinfo.h>
#include <k3bglobals.h>
#include <k3bthroughputestimator.h>
#include "k3bgrowisofshandler.h"
#include <k3bpipebuffer.h>
#include <k3bglobalsettings.h>
#include <k3bdeviceglobals.h>
#include <klocale.h>
#include <kdebug.h>
#include <kglobal.h>
#include <tqvaluelist.h>
#include <tqfile.h>
#include <unistd.h>
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* tqparent, const char* name )
: K3bAbstractWriter( dev, hdl, tqparent, 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 tqfind %1 executable.").tqarg("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.").tqarg(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").tqarg("growisofs")
.tqarg(d->growisofsBin->version).tqarg(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.").tqarg(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").tqarg( 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").tqarg(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<TQCString>& args = d->process->args();
TQString s;
for( TQValueList<TQCString>::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.").tqarg(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.tqcontains( "remaining" ) ) {
if( !d->writingStarted ) {
d->writingStarted = true;
emit newSubTask( i18n("Writing data") );
}
// parse progress
int pos = line.tqfind( "/" );
unsigned long long done = line.left( pos ).toULongLong();
bool ok = true;
d->overallSizeFromOutput = line.mid( pos+1, line.tqfind( "(", 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.tqfind( '@' );
if( pos != -1 ) {
pos += 1;
double speed = line.mid( pos, line.tqfind( '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.tqfind( 'x', pos ) - pos ) << "'" << endl;
}
else {
d->speedEst->dataWritten( done/1024 );
}
}
else
kdDebug() << "(K3bGrowisofsWriter) progress parsing failed: '"
<< line.mid( pos+1, line.tqfind( "(", 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)")
.tqarg(s).tqarg(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"