File indexing completed on 2024-04-28 04:49:43

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