summaryrefslogtreecommitdiffstats
path: root/libk3b/projects/k3bgrowisofswriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libk3b/projects/k3bgrowisofswriter.cpp')
-rw-r--r--libk3b/projects/k3bgrowisofswriter.cpp630
1 files changed, 630 insertions, 0 deletions
diff --git a/libk3b/projects/k3bgrowisofswriter.cpp b/libk3b/projects/k3bgrowisofswriter.cpp
new file mode 100644
index 0000000..3144547
--- /dev/null
+++ b/libk3b/projects/k3bgrowisofswriter.cpp
@@ -0,0 +1,630 @@
+/*
+ *
+ * $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 <qvaluelist.h>
+#include <qfile.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;
+ QString image;
+
+ bool success;
+ bool canceled;
+ bool finished;
+
+ QTime 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;
+
+ QFile inputFile;
+
+ bool usingRingBuffer;
+ K3bPipeBuffer* ringBuffer;
+
+ QString multiSessionInfo;
+
+ bool forceNoEject;
+};
+
+
+K3bGrowisofsWriter::K3bGrowisofsWriter( K3bDevice::Device* dev, K3bJobHandler* hdl,
+ QObject* parent, const char* name )
+ : K3bAbstractWriter( dev, hdl, parent, name )
+{
+ d = new Private;
+ d->speedEst = new K3bThroughputEstimator( this );
+ connect( d->speedEst, SIGNAL(throughput(int)),
+ this, SLOT(slotThroughput(int)) );
+
+ d->gh = new K3bGrowisofsHandler( this );
+ connect( d->gh, SIGNAL(infoMessage(const QString&, int)),
+ this,SIGNAL(infoMessage(const QString&, int)) );
+ connect( d->gh, SIGNAL(newSubTask(const QString&)),
+ this, SIGNAL(newSubTask(const QString&)) );
+ connect( d->gh, SIGNAL(buffer(int)),
+ this, SIGNAL(buffer(int)) );
+ connect( d->gh, SIGNAL(deviceBuffer(int)),
+ this, SIGNAL(deviceBuffer(int)) );
+ connect( d->gh, SIGNAL(flushingCache()),
+ this, 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, SIGNAL(stderrLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) );
+ connect( d->process, SIGNAL(stdoutLine(const QString&)), this, SLOT(slotReceivedStderr(const QString&)) );
+ connect( d->process, SIGNAL(processExited(KProcess*)), this, 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 ) );
+
+ QString 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:" + QString::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:" + QString::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:" + QString::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 << QString("-speed=%1").arg( speed%1385 > 0
+ ? QString::number( (float)speed/1385.0, 'f', 1 )
+ : QString::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 << QString("-use-the-force-luke=bufsize:%1m").arg(bufSize);
+ }
+
+ // additional user parameters from config
+ const QStringList& params = d->growisofsBin->userParameters();
+ for( QStringList::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 = QTime::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 QValueList<QCString>& args = d->process->args();
+ QString s;
+ for( QValueList<QCString>::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, SIGNAL(percent(int)), this, SIGNAL(buffer(int)) );
+ connect( d->ringBuffer, SIGNAL(finished(bool)), this, 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 QString& filename )
+{
+ d->image = filename;
+}
+
+
+void K3bGrowisofsWriter::slotReceivedStderr( const QString& 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() ),
+ SIGNAL(finished(K3bDevice::DeviceHandler*)),
+ this,
+ 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 QString& info )
+{
+ d->multiSessionInfo = info;
+}
+
+
+void K3bGrowisofsWriter::setForceNoEject( bool b )
+{
+ d->forceNoEject = b;
+}
+
+#include "k3bgrowisofswriter.moc"