File indexing completed on 2024-05-05 04:51:12

0001 /*
0002     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <config-k3b.h>
0008 
0009 #include "k3bcdrecordwriter.h"
0010 
0011 #include "k3bcore.h"
0012 #include "k3bexternalbinmanager.h"
0013 #include "k3bprocess.h"
0014 #include "k3bdevice.h"
0015 #include "k3bdeviceglobals.h"
0016 #include "k3bdevicemanager.h"
0017 #include "k3bdevicehandler.h"
0018 #include "k3bglobals.h"
0019 #include "k3bthroughputestimator.h"
0020 #include "k3bglobalsettings.h"
0021 
0022 #include <QDebug>
0023 #include <QString>
0024 #include <QStringList>
0025 #include <QRegExp>
0026 #include <QRegularExpression>
0027 #include <QFile>
0028 #include "k3b_i18n.h"
0029 
0030 #include <QTemporaryFile>
0031 
0032 
0033 Q_DECLARE_METATYPE( QProcess::ExitStatus )
0034 
0035 
0036 class K3b::CdrecordWriter::Private
0037 {
0038 public:
0039     Private()
0040         : cdTextFile(0) {
0041     }
0042 
0043     const ExternalBin* cdrecordBinObject;
0044     Process process;
0045 
0046     WritingMode writingMode;
0047     FormattingMode formattingMode;
0048     bool totalTracksParsed;
0049     bool clone;
0050     bool cue;
0051     bool multi;
0052     bool force;
0053     bool formatting;
0054 
0055     QString cueFile;
0056     QStringList arguments;
0057 
0058     int currentTrack;
0059     int totalTracks;
0060     int totalSize;
0061     int alreadyWritten;
0062 
0063     int lastFifoValue;
0064 
0065     int cdrecordError;
0066     bool writingStarted;
0067 
0068     QByteArray rawCdText;
0069 
0070     K3b::ThroughputEstimator* speedEst;
0071     bool canceled;
0072     bool usingBurnfree;
0073     int usedSpeed;
0074 
0075     struct Track {
0076         int size;
0077         bool audio;
0078     };
0079 
0080     QList<Track> tracks;
0081 
0082     QTemporaryFile* cdTextFile;
0083 
0084     Device::MediaType burnedMediaType;
0085     K3b::Device::SpeedMultiplicator usedSpeedFactor;
0086 };
0087 
0088 
0089 K3b::CdrecordWriter::CdrecordWriter( K3b::Device::Device* dev, K3b::JobHandler* hdl,
0090                                       QObject* parent )
0091     : K3b::AbstractWriter( dev, hdl, parent )
0092 {
0093     d = new Private();
0094     d->speedEst = new K3b::ThroughputEstimator( this );
0095     connect( d->speedEst, SIGNAL(throughput(int)),
0096              this, SLOT(slotThroughput(int)) );
0097 
0098     d->writingMode = K3b::WritingModeTao;
0099     d->formattingMode = K3b::FormattingQuick;
0100     d->clone = false;
0101     d->cue = false;
0102     d->multi = false;
0103     d->force = false;
0104     d->formatting = false;
0105 
0106     d->process.setSplitStdout(true);
0107     d->process.setSuppressEmptyLines(true);
0108     d->process.setFlags( K3bQProcess::RawStdin );
0109     connect( &d->process, SIGNAL(stdoutLine(QString)), this, SLOT(slotStdLine(QString)) );
0110 
0111     // we use a queued connection to give the process object time to wrap up and return to a correct state
0112     qRegisterMetaType<QProcess::ExitStatus>();
0113     connect( &d->process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus)), Qt::QueuedConnection );
0114 }
0115 
0116 
0117 K3b::CdrecordWriter::~CdrecordWriter()
0118 {
0119     delete d->cdTextFile;
0120     delete d;
0121 }
0122 
0123 
0124 bool K3b::CdrecordWriter::active() const
0125 {
0126     return d->process.isRunning();
0127 }
0128 
0129 
0130 QIODevice* K3b::CdrecordWriter::ioDevice() const
0131 {
0132     return &d->process;
0133 }
0134 
0135 
0136 void K3b::CdrecordWriter::setDao( bool b )
0137 {
0138     d->writingMode = ( b ? K3b::WritingModeSao : K3b::WritingModeTao );
0139 }
0140 
0141 
0142 void K3b::CdrecordWriter::setCueFile( const QString& s)
0143 {
0144     d->cue = true;
0145     d->cueFile = s;
0146 
0147     // cuefile only works in DAO mode
0148     setWritingMode( K3b::WritingModeSao );
0149 }
0150 
0151 
0152 void K3b::CdrecordWriter::setClone( bool b )
0153 {
0154     d->clone = b;
0155 }
0156 
0157 
0158 void K3b::CdrecordWriter::setRawCdText( const QByteArray& a )
0159 {
0160     d->rawCdText = a;
0161 }
0162 
0163 
0164 void K3b::CdrecordWriter::setWritingMode( K3b::WritingMode mode )
0165 {
0166     d->writingMode = mode;
0167 }
0168 
0169 
0170 void K3b::CdrecordWriter::setFormattingMode( FormattingMode mode )
0171 {
0172     d->formattingMode = mode;
0173     d->formatting = true;
0174 }
0175 
0176 
0177 void K3b::CdrecordWriter::setMulti( bool b )
0178 {
0179     d->multi = b;
0180 }
0181 
0182 
0183 void K3b::CdrecordWriter::setForce( bool b )
0184 {
0185     d->force = b;
0186 }
0187 
0188 
0189 bool K3b::CdrecordWriter::prepareProcess()
0190 {
0191     d->cdrecordBinObject = k3bcore->externalBinManager()->binObject("cdrecord");
0192 
0193     if( !d->cdrecordBinObject ) {
0194         emit infoMessage( i18n("Could not find %1 executable.", QLatin1String("cdrecord")), MessageError );
0195         return false;
0196     }
0197 
0198     d->process.clearProgram();
0199 
0200     d->burnedMediaType = burnDevice()->mediaType();
0201 
0202     d->process << d->cdrecordBinObject;
0203 
0204     // display progress
0205     d->process << "-v";
0206 #ifdef K3B_DEBUG
0207     d->process << "-V";
0208 #endif
0209     if( d->cdrecordBinObject->hasFeature( "gracetime") )
0210         d->process << "gracetime=2";  // 2 is the lowest allowed value (Joerg, why do you do this to us?)
0211 
0212     // Again we assume the device to be set!
0213     d->process << QString("dev=%1").arg(K3b::externalBinDeviceParameter(burnDevice(), d->cdrecordBinObject));
0214 
0215     d->usedSpeedFactor = K3b::speedMultiplicatorForMediaType( d->burnedMediaType );
0216     d->usedSpeed = burnSpeed();
0217     if( d->usedSpeed == 0 ) {
0218         // try to determine the writeSpeed
0219         // if it fails determineMaximalWriteSpeed() will return 0 and
0220         // the choice is left to cdrecord
0221         d->usedSpeed = burnDevice()->determineMaximalWriteSpeed();
0222     }
0223 
0224     if( d->usedSpeed != 0 )
0225         d->process << QString("speed=%1").arg( formatWritingSpeedFactor( d->usedSpeed, d->burnedMediaType, SpeedFormatInteger ) );
0226 
0227 
0228     if ( K3b::Device::isBdMedia( d->burnedMediaType ) ) {
0229         if ( !d->cdrecordBinObject->hasFeature( "blu-ray" ) ) {
0230             emit infoMessage( i18n( "Cdrecord version %1 does not support Blu-ray writing." ,d->cdrecordBinObject->version() ), MessageError );
0231             // FIXME: add a way to fail the whole thing here
0232         }
0233         d->process << "-sao";
0234     }
0235     else if ( K3b::Device::isDvdMedia( d->burnedMediaType ) ) {
0236         // cdrecord only supports SAO for DVD
0237         d->process << "-sao";
0238 
0239 
0240 #ifdef __GNUC__
0241 #warning Enable layer jump mode: add it to K3b::WritingMode and to the GUI
0242 #endif
0243         // if( d->writingMode == Device::WRITINGMODE_LAYER_JUMP ) {
0244 //             d->process << "-driveropts=layerbreak";
0245 //         }
0246     }
0247     else if( K3b::Device::isCdMedia( d->burnedMediaType ) ) {
0248         if( d->writingMode == K3b::WritingModeSao || d->cue ) {
0249             if( burnDevice()->dao() )
0250                 d->process << "-sao";
0251             else {
0252                 if( d->cdrecordBinObject->hasFeature( "tao" ) )
0253                     d->process << "-tao";
0254                 emit infoMessage( i18n("Writer does not support disk at once (DAO) recording"), MessageWarning );
0255             }
0256         }
0257         else if( d->writingMode == K3b::WritingModeRaw ) {
0258             if( burnDevice()->supportsWritingMode( K3b::Device::WRITINGMODE_RAW_R96R ) )
0259                 d->process << "-raw96r";
0260             else if( burnDevice()->supportsWritingMode( K3b::Device::WRITINGMODE_RAW_R16 ) )
0261                 d->process << "-raw16";
0262             else if( burnDevice()->supportsWritingMode( K3b::Device::WRITINGMODE_RAW_R96P ) )
0263                 d->process << "-raw96p";
0264             else {
0265                 emit infoMessage( i18n("Writer does not support raw writing."), MessageWarning );
0266                 if( d->cdrecordBinObject->hasFeature( "tao" ) )
0267                     d->process << "-tao";
0268             }
0269         }
0270         else if( d->cdrecordBinObject->hasFeature( "tao" ) )
0271             d->process << "-tao";
0272     }
0273     else {
0274         emit infoMessage( i18n( "Cdrecord does not support writing %1 media." , K3b::Device::mediaTypeString( d->burnedMediaType ) ), MessageError );
0275         // FIXME: add a way to fail the whole thing here
0276     }
0277 
0278     if( simulate() )
0279         d->process << "-dummy";
0280 
0281     d->usingBurnfree = false;
0282     if( k3bcore->globalSettings()->burnfree() ) {
0283         if( burnDevice()->burnproof() ) {
0284 
0285             d->usingBurnfree = true;
0286 
0287             // with cdrecord 1.11a02 burnproof was renamed to burnfree
0288             if( d->cdrecordBinObject->hasFeature( "burnproof" ) )
0289                 d->process << "driveropts=burnproof";
0290             else
0291                 d->process << "driveropts=burnfree";
0292         }
0293         else
0294             emit infoMessage( i18n("Writer does not support buffer underrun free recording (Burnfree)"), MessageWarning );
0295     }
0296 
0297     if( k3bcore->globalSettings()->force() || d->force ) {
0298         d->process << "-force";
0299         emit infoMessage( i18n("'Force unsafe operations' enabled."), MessageWarning );
0300     }
0301 
0302     if( d->cue ) {
0303         d->process.setWorkingDirectory( d->cueFile );
0304         d->process << QString("cuefile=%1").arg( d->cueFile );
0305     }
0306 
0307     if( d->clone )
0308         d->process << "-clone";
0309 
0310     if( d->multi )
0311         d->process << "-multi";
0312 
0313     if( d->formatting ) {
0314         switch( d->formattingMode ) {
0315             case FormattingComplete:
0316                 d->process << "blank=all";
0317                 break;
0318             case FormattingQuick:
0319                 d->process << "blank=fast";
0320                 break;
0321         }
0322     }
0323 
0324     if( d->rawCdText.size() > 0 ) {
0325         delete d->cdTextFile;
0326         d->cdTextFile = new QTemporaryFile();
0327         if ( !d->cdTextFile->open() ||
0328              d->cdTextFile->write( d->rawCdText ) != d->rawCdText.size() ||
0329             !d->cdTextFile->flush() ) {
0330             emit infoMessage( i18n( "Failed to write temporary file '%1'", d->cdTextFile->fileName() ), MessageError );
0331             return false;
0332         }
0333         d->process << "textfile=" + d->cdTextFile->fileName();
0334     }
0335 
0336     bool manualBufferSize = k3bcore->globalSettings()->useManualBufferSize();
0337     if( manualBufferSize ) {
0338         d->process << QString("fs=%1m").arg( k3bcore->globalSettings()->bufferSize() );
0339     }
0340 
0341     bool overburn = k3bcore->globalSettings()->overburn();
0342     if( overburn ) {
0343         if( d->cdrecordBinObject->hasFeature("overburn") ) {
0344             if ( k3bcore->globalSettings()->force() )
0345                 d->process << "-ignsize";
0346             else
0347                 d->process << "-overburn";
0348         }
0349         else {
0350             emit infoMessage( i18n("Cdrecord %1 does not support overburning.",d->cdrecordBinObject->version()), MessageWarning );
0351         }
0352     }
0353 
0354     // additional user parameters from config
0355     const QStringList& params = d->cdrecordBinObject->userParameters();
0356     for( QStringList::const_iterator it = params.constBegin(); it != params.constEnd(); ++it )
0357         d->process << *it;
0358 
0359     // add the user parameters
0360     for( QStringList::const_iterator it = d->arguments.constBegin(); it != d->arguments.constEnd(); ++it )
0361         d->process << *it;
0362 
0363     return true;
0364 }
0365 
0366 
0367 K3b::CdrecordWriter* K3b::CdrecordWriter::addArgument( const QString& arg )
0368 {
0369     d->arguments.append( arg );
0370     return this;
0371 }
0372 
0373 
0374 void K3b::CdrecordWriter::clearArguments()
0375 {
0376     d->arguments.clear();
0377 }
0378 
0379 
0380 void K3b::CdrecordWriter::start()
0381 {
0382     jobStarted();
0383 
0384     d->canceled = false;
0385     d->speedEst->reset();
0386     d->writingStarted = false;
0387 
0388     if ( !prepareProcess() ) {
0389         jobFinished(false);
0390         return;
0391     }
0392 
0393     emit debuggingOutput( QLatin1String( "Used versions" ), QString::fromLatin1( "cdrecord: %1" ).arg(d->cdrecordBinObject->version()) );
0394 
0395     if( !d->cdrecordBinObject->copyright().isEmpty() )
0396         emit infoMessage( i18n("Using %1 %2 – Copyright © %3"
0397                                ,(d->cdrecordBinObject->hasFeature( "wodim" ) ? "Wodim" : "Cdrecord" )
0398                                ,d->cdrecordBinObject->version()
0399                                ,d->cdrecordBinObject->copyright()), MessageInfo );
0400 
0401 
0402     qDebug() << "***** " << d->cdrecordBinObject->name() << " parameters:\n";
0403     QString s = d->process.joinedArgs();
0404     qDebug() << s << Qt::flush;
0405     emit debuggingOutput( d->cdrecordBinObject->name() + " command:", s);
0406 
0407     d->currentTrack = 0;
0408     d->cdrecordError = UNKNOWN;
0409     d->totalTracksParsed = false;
0410     d->alreadyWritten = 0;
0411     d->tracks.clear();
0412     d->totalSize = 0;
0413 
0414     emit newSubTask( i18n("Preparing write process...") );
0415 
0416     // FIXME: check the return value
0417     if( K3b::isMounted( burnDevice() ) ) {
0418         emit infoMessage( i18n("Unmounting medium"), MessageInfo );
0419         K3b::unmount( burnDevice() );
0420     }
0421 
0422     // block the device (including certain checks)
0423     k3bcore->blockDevice( burnDevice() );
0424 
0425     // lock the device for good in this process since it will
0426     // be opened in the cdrecord process
0427     burnDevice()->close();
0428     burnDevice()->usageLock();
0429 
0430     if( !d->process.start( KProcess::MergedChannels ) ) {
0431         // something went wrong when starting the program
0432         // it "should" be the executable
0433         qDebug() << "(K3b::CdrecordWriter) could not start " << d->cdrecordBinObject->name();
0434         emit infoMessage( i18n("Could not start %1.",d->cdrecordBinObject->name()), K3b::Job::MessageError );
0435         jobFinished(false);
0436     }
0437     else {
0438         const QString formattedSpeed = formatWritingSpeedFactor( d->usedSpeed, d->burnedMediaType, SpeedFormatInteger );
0439         const QString formattedMode = writingModeString( d->writingMode );
0440         // FIXME: these messages should also take DVD into account.
0441         if( simulate() ) {
0442             emit newTask( i18n("Simulating") );
0443             if ( d->burnedMediaType & Device::MEDIA_DVD_PLUS_ALL )
0444                 // xgettext: no-c-format
0445                 emit infoMessage( i18n("Starting simulation at %1x speed...", formattedSpeed ), Job::MessageInfo );
0446             else
0447                 emit infoMessage( i18n("Starting %1 simulation at %2x speed...", formattedMode, formattedSpeed ), Job::MessageInfo );
0448         }
0449         else {
0450             emit newTask( i18n("Writing") );
0451             if ( d->burnedMediaType & Device::MEDIA_DVD_PLUS_ALL )
0452                 // xgettext: no-c-format
0453                 emit infoMessage( i18n("Starting writing at %1x speed...", formattedSpeed ), Job::MessageInfo );
0454             else
0455                 emit infoMessage( i18n("Starting %1 writing at %2x speed...", formattedMode, formattedSpeed ), Job::MessageInfo );
0456         }
0457     }
0458 }
0459 
0460 
0461 void K3b::CdrecordWriter::cancel()
0462 {
0463     if( active() ) {
0464         d->canceled = true;
0465         if( d->process.isRunning() )
0466             d->process.terminate();
0467     }
0468 }
0469 
0470 
0471 void K3b::CdrecordWriter::slotStdLine( const QString& line )
0472 {
0473     static QRegExp s_burnfreeCounterRx( "^BURN\\-Free\\swas\\s(\\d+)\\stimes\\sused" );
0474     static QRegExp s_burnfreeCounterRxPredict( "^Total\\sof\\s(\\d+)\\s\\spossible\\sbuffer\\sunderruns\\spredicted" );
0475 
0476     // tracknumber: cap(1)
0477     // done: cap(2)
0478     // complete: cap(3)
0479     // fifo: cap(4)  (it seems as if some patched cdrecord versions do not emit the fifo info but only the buf... :(
0480     // buffer: cap(5)
0481     static QRegExp s_progressRx( "Track\\s(\\d\\d)\\:\\s*(\\d*)\\sof\\s*(\\d*)\\sMB\\swritten\\s(?:\\(fifo\\s*(\\d*)\\%\\)\\s*)?(?:\\[buf\\s*(\\d*)\\%\\])?.*" );
0482 
0483     emit debuggingOutput( d->cdrecordBinObject->name(), line );
0484 
0485     //
0486     // Progress and toc parsing
0487     //
0488 
0489     if( line.startsWith( "Track " ) ) {
0490         if( !d->totalTracksParsed ) {
0491             // this is not the progress display but the list of tracks that will get written
0492             // we always extract the tracknumber to get the highest at last
0493             bool ok;
0494             int tt = line.mid( 6, 2 ).toInt(&ok);
0495 
0496             if( ok ) {
0497                 struct Private::Track track;
0498                 track.audio  = ( line.mid( 10, 5 ) == "audio" );
0499 
0500                 d->totalTracks = tt;
0501 
0502                 static const QRegularExpression rx("\\d");
0503                 int sizeStart = line.indexOf( rx, 10 );
0504                 int sizeEnd = line.indexOf( "MB", sizeStart );
0505                 track.size = line.mid( sizeStart, sizeEnd-sizeStart ).toInt(&ok);
0506 
0507                 if( ok ) {
0508                     d->tracks.append(track);
0509                     d->totalSize += track.size;
0510                 }
0511                 else
0512                     qDebug() << "(K3b::CdrecordWriter) track number parse error: "
0513                              << line.mid( sizeStart, sizeEnd-sizeStart );
0514             }
0515             else
0516                 qDebug() << "(K3b::CdrecordWriter) track number parse error: "
0517                          << line.mid( 6, 2 );
0518         }
0519 
0520         else if( s_progressRx.exactMatch( line ) ) {
0521             //      int num = s_progressRx.cap(1).toInt();
0522             int made = s_progressRx.cap(2).toInt();
0523             int size = s_progressRx.cap(3).toInt();
0524             int fifo = s_progressRx.cap(4).toInt();
0525 
0526             emit buffer( fifo );
0527             d->lastFifoValue = fifo;
0528 
0529             if( s_progressRx.captureCount() > 4 )
0530                 emit deviceBuffer( s_progressRx.cap(5).toInt() );
0531 
0532             //
0533             // cdrecord's output sucks a bit.
0534             // we get track sizes that differ from the sizes in the progress
0535             // info since these are dependent on the writing mode.
0536             // so we just use the track sizes and do a bit of math...
0537             //
0538 
0539             if( d->tracks.count() > d->currentTrack-1 && size > 0 ) {
0540                 double convV = (double)d->tracks[d->currentTrack-1].size/(double)size;
0541                 made = (int)((double)made * convV);
0542                 size = d->tracks[d->currentTrack-1].size;
0543             }
0544             else {
0545                 qCritical() << "(K3b::CdrecordWriter) Did not parse all tracks sizes!" << Qt::endl;
0546             }
0547 
0548             if( !d->writingStarted ) {
0549                 d->writingStarted = true;
0550                 emit newSubTask( i18n("Writing data") );
0551             }
0552 
0553             if( size > 0 ) {
0554                 emit processedSubSize( made, size );
0555                 emit subPercent( 100*made/size );
0556             }
0557 
0558             if( d->totalSize > 0 ) {
0559                 emit processedSize( d->alreadyWritten+made, d->totalSize );
0560                 emit percent( 100*(d->alreadyWritten+made)/d->totalSize );
0561             }
0562 
0563             d->speedEst->dataWritten( (d->alreadyWritten+made)*1024 );
0564         }
0565     }
0566 
0567     //
0568     // Cdrecord starts all error and warning messages with it's path
0569     // With Debian's script it starts with cdrecord (or /usr/bin/cdrecord or whatever! I hate this script!)
0570     //
0571 
0572     else if( line.startsWith( "cdrecord" ) ||
0573              line.startsWith( d->cdrecordBinObject->path() ) ||
0574              line.startsWith( d->cdrecordBinObject->path().left(d->cdrecordBinObject->path().length()-5) ) ) {
0575         // get rid of the path and the following colon and space
0576         QString errStr = line.mid( line.indexOf(':') + 2 );
0577 
0578         if( errStr.startsWith( "Drive does not support SAO" ) ) {
0579             emit infoMessage( i18n("DAO (Disk At Once) recording not supported with this writer"), K3b::Job::MessageError );
0580             emit infoMessage( i18n("Please choose TAO (Track At Once) and try again"), K3b::Job::MessageError );
0581         }
0582         else if( errStr.startsWith( "Drive does not support RAW" ) ) {
0583             emit infoMessage( i18n("RAW recording not supported with this writer"), K3b::Job::MessageError );
0584         }
0585         else if( errStr.startsWith("Input/output error.") ) {
0586             emit infoMessage( i18n("Input/output error. Not necessarily serious."), MessageWarning );
0587         }
0588         else if( errStr.startsWith("shmget failed") ) {
0589             d->cdrecordError = SHMGET_FAILED;
0590         }
0591         else if( errStr.startsWith("OPC failed") ) {
0592             d->cdrecordError = OPC_FAILED;
0593         }
0594         else if( errStr.startsWith( "Drive needs to reload the media" ) ) {
0595             emit infoMessage( i18n("Reloading of medium required"), K3b::Job::MessageInfo );
0596         }
0597         else if( errStr.startsWith( "The current problem looks like a buffer underrun" ) ) {
0598             if( d->cdrecordError == UNKNOWN ) // it is almost never a buffer underrun these days.
0599                 d->cdrecordError = BUFFER_UNDERRUN;
0600         }
0601         else if( errStr.startsWith("MessageWarning: Data may not fit") ) {
0602             bool overburn = k3bcore->globalSettings()->overburn();
0603             if( overburn && d->cdrecordBinObject->hasFeature("overburn") )
0604                 emit infoMessage( i18n("Trying to write more than the official disk capacity"), K3b::Job::MessageWarning );
0605             d->cdrecordError = OVERSIZE;
0606         }
0607         else if( errStr.startsWith("Bad Option") ) {
0608             d->cdrecordError = BAD_OPTION;
0609             // parse option
0610             int pos = line.indexOf( "Bad Option" ) + 12;
0611             int len = line.length() - pos - 1;
0612             emit infoMessage( i18n("No valid %1 option: %2",d->cdrecordBinObject->name(),line.mid(pos, len)),
0613                               MessageError );
0614         }
0615         else if( errStr.startsWith("Cannot set speed/dummy") ) {
0616             d->cdrecordError = CANNOT_SET_SPEED;
0617         }
0618         else if( errStr.startsWith("Cannot open new session") ) {
0619             d->cdrecordError = CANNOT_OPEN_NEW_SESSION;
0620         }
0621         else if( errStr.startsWith("Cannot send CUE sheet") ) {
0622             d->cdrecordError = CANNOT_SEND_CUE_SHEET;
0623         }
0624         else if( errStr.startsWith( "Trying to use ultra high speed" ) ||
0625                  errStr.startsWith( "Trying to use high speed" ) ||
0626                  errStr.startsWith( "Probably trying to use ultra high speed" ) ||
0627                  errStr.startsWith( "You did use a high speed medium on an improper writer" ) ||
0628                  errStr.startsWith( "You did use a ultra high speed medium on an improper writer" ) ) {
0629             d->cdrecordError = HIGH_SPEED_MEDIUM;
0630         }
0631         else if( errStr.startsWith( "You may have used an ultra low speed medium" ) ) {
0632             d->cdrecordError = LOW_SPEED_MEDIUM;
0633         }
0634         else if( errStr.startsWith( "Permission denied. Cannot open" ) ||
0635                  errStr.startsWith( "Operation not permitted." ) ) {
0636             d->cdrecordError = PERMISSION_DENIED;
0637         }
0638         else if( errStr.startsWith( "Can only copy session # 1") ) {
0639             emit infoMessage( i18n("Only session 1 will be cloned."), MessageWarning );
0640         }
0641         else if( errStr == "Cannot fixate disk." ) {
0642             emit infoMessage( i18n("Unable to fixate the disk."), MessageError );
0643             if( d->cdrecordError == UNKNOWN )
0644                 d->cdrecordError = CANNOT_FIXATE_DISK;
0645         }
0646         else if( errStr == "A write error occurred." ) {
0647             d->cdrecordError = WRITE_ERROR;
0648         }
0649         else if( errStr.startsWith( "Try again with cdrecord blank=all." ) ) {
0650             d->cdrecordError = BLANK_FAILED;
0651         }
0652         else if( errStr.startsWith( "faio_wait_on_buffer for reader timed out" ) ) {
0653             d->cdrecordError = SHORT_READ;
0654         }
0655     }
0656 
0657     //
0658     // All other messages
0659     //
0660 
0661     else if( line.contains( "at speed" ) ) {
0662         // parse the speed and inform the user if cdrdao switched it down
0663         const int pos = line.indexOf( "at speed" );
0664         const int pos2 = line.indexOf( "in", pos+9 );
0665         const int speed( double( K3b::speedMultiplicatorForMediaType( d->burnedMediaType ) ) * line.mid( pos+9, pos2-pos-10 ).toDouble() );  // cdrecord-dvd >= 2.01a25 uses 8.0 and stuff
0666         if( speed > 0 && double( qAbs( speed - d->usedSpeed ) ) > 0.5*double( K3b::speedMultiplicatorForMediaType( d->burnedMediaType ) ) ) {
0667             // xgettext: no-c-format
0668             emit infoMessage( i18n("Medium or burner does not support writing at %1x speed", formatWritingSpeedFactor( d->usedSpeed, d->burnedMediaType ) ),
0669                               K3b::Job::MessageWarning );
0670             if( speed > d->usedSpeed )
0671                 // xgettext: no-c-format
0672                 emit infoMessage( i18n("Switching burn speed up to %1x", formatWritingSpeedFactor( speed, d->burnedMediaType ) ), K3b::Job::MessageWarning );
0673             else
0674                 // xgettext: no-c-format
0675                 emit infoMessage( i18n("Switching burn speed down to %1x", formatWritingSpeedFactor( speed, d->burnedMediaType ) ), K3b::Job::MessageWarning );
0676         }
0677     }
0678     else if( line.startsWith( "Starting new" ) ) {
0679         d->totalTracksParsed = true;
0680         if( d->currentTrack > 0 ) {// nothing has been written at the start of track 1
0681             if( d->tracks.count() > d->currentTrack-1 )
0682                 d->alreadyWritten += d->tracks[d->currentTrack-1].size;
0683             else
0684                 qCritical() << "(K3b::CdrecordWriter) Did not parse all tracks sizes!";
0685         }
0686         else
0687             emit infoMessage( i18n("Starting disc write"), MessageInfo );
0688 
0689         d->currentTrack++;
0690 
0691         if( d->currentTrack > d->tracks.count() ) {
0692             qDebug() << "(K3b::CdrecordWriter) need to add dummy track struct.";
0693             struct Private::Track t;
0694             t.size = 1;
0695             t.audio = false;
0696             d->tracks.append(t);
0697         }
0698 
0699         qDebug() << "(K3b::CdrecordWriter) writing track " << d->currentTrack << " of " << d->totalTracks << " tracks.";
0700         emit nextTrack( d->currentTrack, d->totalTracks );
0701     }
0702     else if( line.startsWith( "Fixating" ) ) {
0703         emit newSubTask( i18n("Closing Session") );
0704     }
0705     else if( line.startsWith( "Writing lead-in" ) ) {
0706         d->totalTracksParsed = true;
0707         emit newSubTask( i18n("Writing Leadin") );
0708     }
0709     else if( line.startsWith( "Writing Leadout") ) {
0710         emit newSubTask( i18n("Writing Leadout") );
0711     }
0712     else if( line.startsWith( "Writing pregap" ) ) {
0713         emit newSubTask( i18n("Writing pregap") );
0714     }
0715     else if( line.startsWith( "Performing OPC" ) ) {
0716         emit infoMessage( i18n("Performing Optimum Power Calibration"), K3b::Job::MessageInfo );
0717     }
0718     else if( line.startsWith( "Sending" ) ) {
0719         emit infoMessage( i18n("Sending CUE sheet"), K3b::Job::MessageInfo );
0720     }
0721     else if( line.startsWith( "Turning BURN-Free on" ) || line.startsWith( "BURN-Free is ON") ) {
0722         emit infoMessage( i18n("Enabled Burnfree"), K3b::Job::MessageInfo );
0723     }
0724     else if( line.startsWith( "Turning BURN-Free off" ) ) {
0725         emit infoMessage( i18n("Disabled Burnfree"), K3b::Job::MessageWarning );
0726     }
0727     else if( line.startsWith( "Re-load disk and hit" ) ) {
0728         // this happens on some notebooks where cdrecord is not able to close the
0729         // tray itself, so we need to ask the user to do so
0730         blockingInformation( i18n("Please reload the medium and press 'OK'"),
0731                              i18n("Unable to close the tray") );
0732 
0733         // now send a <CR> to cdrecord
0734         // hopefully this will do it since I have no possibility to test it!
0735         d->process.write( "\n", 1 );
0736     }
0737     else if( s_burnfreeCounterRx.indexIn( line ) ) {
0738         bool ok;
0739         int num = s_burnfreeCounterRx.cap(1).toInt(&ok);
0740         if( ok )
0741             emit infoMessage( i18np("Burnfree was used once.", "Burnfree was used %1 times.", num), MessageInfo );
0742     }
0743     else if( s_burnfreeCounterRxPredict.indexIn( line ) ) {
0744         bool ok;
0745         int num = s_burnfreeCounterRxPredict.cap(1).toInt(&ok);
0746         if( ok )
0747             emit infoMessage( i18np("Buffer was low once.", "Buffer was low %1 times.", num), MessageInfo );
0748     }
0749     else if( line.contains("Medium Error") ) {
0750         d->cdrecordError = MEDIUM_ERROR;
0751     }
0752     else if( line.startsWith( "Error trying to open" ) && line.contains( "(Device or resource busy)" ) ) {
0753         d->cdrecordError = DEVICE_BUSY;
0754     }
0755     else {
0756         // debugging
0757         qDebug() << "(" << d->cdrecordBinObject->name() << ") " << line;
0758     }
0759 }
0760 
0761 
0762 void K3b::CdrecordWriter::slotProcessExited( int exitCode, QProcess::ExitStatus exitStatus )
0763 {
0764     // remove temporary cdtext file
0765     delete d->cdTextFile;
0766     d->cdTextFile = 0;
0767 
0768     // release the device within this process
0769     burnDevice()->usageUnlock();
0770 
0771     // unblock the device
0772     k3bcore->unblockDevice( burnDevice() );
0773 
0774     if( d->canceled ) {
0775         // this will unblock and eject the drive and emit the finished/canceled signals
0776         K3b::AbstractWriter::cancel();
0777         return;
0778     }
0779 
0780 
0781     if( exitStatus == QProcess::NormalExit ) {
0782         switch( exitCode ) {
0783         case 0:
0784         {
0785             if( d->formatting )
0786                 emit infoMessage( i18n("Erasing successfully completed"), K3b::Job::MessageSuccess );
0787             else if( simulate() )
0788                 emit infoMessage( i18n("Simulation successfully completed"), K3b::Job::MessageSuccess );
0789             else
0790                 emit infoMessage( i18n("Writing successfully completed"), K3b::Job::MessageSuccess );
0791 
0792             if( !d->formatting ) {
0793                 int s = d->speedEst->average();
0794                 emit infoMessage( ki18n("Average overall write speed: %1 KB/s (%2x)" )
0795                                   .subs( s ).subs( ( double )s/( double )d->usedSpeedFactor, 0, 'g', 2 ).toString(),
0796                                   MessageInfo );
0797             }
0798 
0799             jobFinished( true );
0800         }
0801         break;
0802 
0803         default:
0804             qDebug() << "(K3b::CdrecordWriter) error: " << exitCode;
0805 
0806             if( d->cdrecordError == UNKNOWN && d->lastFifoValue <= 3 )
0807                 d->cdrecordError = BUFFER_UNDERRUN;
0808 
0809             switch( d->cdrecordError ) {
0810             case OVERSIZE:
0811                 if( k3bcore->globalSettings()->overburn() &&
0812                     d->cdrecordBinObject->hasFeature("overburn") )
0813                     emit infoMessage( i18n("Data did not fit on disk."), MessageError );
0814                 else {
0815                     emit infoMessage( i18n("Data does not fit on disk."), MessageError );
0816                     if( d->cdrecordBinObject->hasFeature("overburn") )
0817                         emit infoMessage( i18n("Enable overburning in the advanced K3b settings to burn anyway."), MessageInfo );
0818                 }
0819                 break;
0820             case BAD_OPTION:
0821                 // error message has already been emitted earlier since we needed the actual line
0822                 break;
0823             case SHMGET_FAILED:
0824                 emit infoMessage( i18n("%1 could not reserve shared memory segment of requested size.",d->cdrecordBinObject->name()), MessageError );
0825                 emit infoMessage( i18n("Probably you chose a too large buffer size."), MessageError );
0826                 break;
0827             case OPC_FAILED:
0828                 emit infoMessage( i18n("OPC failed. Probably the writer does not like the medium."), MessageError );
0829                 break;
0830             case CANNOT_SET_SPEED:
0831                 emit infoMessage( i18n("Unable to set write speed to %1.",formatWritingSpeedFactor( d->usedSpeed, d->burnedMediaType, SpeedFormatInteger ) ), MessageError );
0832                 emit infoMessage( i18n("Probably this is lower than your writer's lowest writing speed."), MessageError );
0833                 break;
0834             case CANNOT_SEND_CUE_SHEET:
0835                 emit infoMessage( i18n("Unable to send CUE sheet."), MessageError );
0836                 if( d->writingMode == K3b::WritingModeSao )
0837                     emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), MessageError );
0838                 break;
0839             case CANNOT_OPEN_NEW_SESSION:
0840                 emit infoMessage( i18n("Unable to open new session."), MessageError );
0841                 emit infoMessage( i18n("Probably a problem with the medium."), MessageError );
0842                 break;
0843             case CANNOT_FIXATE_DISK:
0844                 emit infoMessage( i18n("The disk might still be readable."), MessageError );
0845                 if( d->writingMode == K3b::WritingModeTao && burnDevice()->dao() )
0846                     emit infoMessage( i18n("Try DAO writing mode."), MessageError );
0847                 break;
0848             case PERMISSION_DENIED:
0849                 emit infoMessage( i18n("%1 has no permission to open the device.",QString("cdrecord")), MessageError );
0850                 emit infoMessage( i18n("Modify device settings in K3b to solve this problem."), MessageError );
0851                 break;
0852             case BUFFER_UNDERRUN:
0853                 emit infoMessage( i18n("Probably a buffer underrun occurred."), MessageError );
0854                 if( !d->usingBurnfree && burnDevice()->burnproof() )
0855                     emit infoMessage( i18n("Please enable Burnfree or choose a lower burning speed."), MessageError );
0856                 else
0857                     emit infoMessage( i18n("Please choose a lower burning speed."), MessageError );
0858                 break;
0859             case HIGH_SPEED_MEDIUM:
0860                 emit infoMessage( i18n("Found a high-speed medium not suitable for the writer being used."), MessageError );
0861                 emit infoMessage( i18n("Use the 'force unsafe operations' option to ignore this."), MessageError );
0862                 break;
0863             case LOW_SPEED_MEDIUM:
0864                 emit infoMessage( i18n("Found a low-speed medium not suitable for the writer being used."), MessageError );
0865                 emit infoMessage( i18n("Use the 'force unsafe operations' option to ignore this."), MessageError );
0866                 break;
0867             case MEDIUM_ERROR:
0868                 emit infoMessage( i18n("Most likely the burning failed due to low-quality media."), MessageError );
0869                 break;
0870             case DEVICE_BUSY:
0871                 emit infoMessage( i18n("Another application is blocking the device (most likely automounting)."), MessageError );
0872                 break;
0873             case WRITE_ERROR:
0874                 emit infoMessage( i18n("A write error occurred."), MessageError );
0875                 if( d->writingMode == K3b::WritingModeSao )
0876                     emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), MessageError );
0877                 break;
0878             case BLANK_FAILED:
0879                 emit infoMessage( i18n("Some drives do not support all erase types."), MessageError );
0880                 emit infoMessage( i18n("Try again using 'Complete' erasing."), MessageError );
0881                 break;
0882             case SHORT_READ:
0883                 emit infoMessage( QLatin1String("Internal error: short read. Please report!"), MessageError );
0884                 break;
0885             case UNKNOWN:
0886                 if( (exitCode == 12) && K3b::kernelVersion() >= K3b::Version( 2, 6, 8 ) && d->cdrecordBinObject->hasFeature( "suidroot" ) ) {
0887                     emit infoMessage( i18n("Since kernel version 2.6.8 cdrecord cannot use SCSI transport when running suid root anymore."), MessageError );
0888                     emit infoMessage( i18n("You may use K3b::Setup to solve this problem or remove the suid bit manually."), MessageError );
0889                 }
0890                 else if( !wasSourceUnreadable() ) {
0891                     emit infoMessage( i18n("%1 returned an unknown error (code %2).",
0892                                            d->cdrecordBinObject->name(), exitCode),
0893                                       K3b::Job::MessageError );
0894 
0895                     if( (exitCode >= 254) && d->writingMode == K3b::WritingModeSao ) {
0896                         emit infoMessage( i18n("Sometimes using TAO writing mode solves this issue."), MessageError );
0897                     }
0898                     else {
0899                         emit infoMessage( i18n("If you are running an unpatched cdrecord version..."), MessageError );
0900                         emit infoMessage( i18n("...and this error also occurs with high quality media..."), MessageError );
0901                         emit infoMessage( i18n("...and the K3b FAQ does not help you..."), MessageError );
0902                         emit infoMessage( i18n("...please include the debugging output in your problem report."), MessageError );
0903                     }
0904                 }
0905                 break;
0906             }
0907             jobFinished( false );
0908         }
0909     }
0910     else {
0911         emit infoMessage( i18n("%1 crashed.", d->cdrecordBinObject->name()),
0912                           MessageError );
0913         jobFinished( false );
0914     }
0915 }
0916 
0917 
0918 void K3b::CdrecordWriter::slotThroughput( int t )
0919 {
0920     emit writeSpeed( t, d->tracks.count() > d->currentTrack && !d->tracks[d->currentTrack-1].audio
0921                      ? K3b::Device::SPEED_FACTOR_CD_MODE1
0922                      : d->usedSpeedFactor );
0923 }
0924 
0925 
0926 qint64 K3b::CdrecordWriter::write( const char* data, qint64 maxSize )
0927 {
0928     return d->process.write( data, maxSize );
0929 }
0930 
0931 #include "moc_k3bcdrecordwriter.cpp"