File indexing completed on 2024-05-05 04:50:55

0001 /*
0002     SPDX-FileCopyrightText: 2011 Michal Malek <michalm@jabster.pl>
0003     SPDX-FileCopyrightText: 1998-2010 Sebastian Trueg <trueg@k3b.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 
0009 #include "k3bcdcopyjob.h"
0010 #include "k3baudiosessionreadingjob.h"
0011 
0012 #include "k3bexternalbinmanager.h"
0013 #include "k3bdevice.h"
0014 #include "k3bdiskinfo.h"
0015 #include "k3btoc.h"
0016 #include "k3bglobals.h"
0017 #include "k3bdevicehandler.h"
0018 #include "k3breadcdreader.h"
0019 #include "k3bdatatrackreader.h"
0020 #include "k3bcdrecordwriter.h"
0021 #include "k3bcdtext.h"
0022 #include "k3bcore.h"
0023 #include "k3binffilewriter.h"
0024 #include "k3bglobalsettings.h"
0025 #include "k3bcddb.h"
0026 #include "k3b_i18n.h"
0027 
0028 #include <KConfig>
0029 #include <KIO/Global>
0030 #include <KIO/DeleteJob>
0031 #include <KIO/Job>
0032 
0033 #include <QDebug>
0034 #include <QDir>
0035 #include <QFile>
0036 #include <QFileInfo>
0037 #include <QRegExp>
0038 #include <QStringList>
0039 #include <QTemporaryFile>
0040 #include <QTimer>
0041 #include <QVector>
0042 #include <QApplication>
0043 
0044 #include <KCDDB/Client>
0045 #include <KCDDB/CDInfo>
0046 
0047 
0048 
0049 class K3b::CdCopyJob::Private
0050 {
0051 public:
0052     Private()
0053         : canceled(false),
0054           running(false),
0055           readcdReader(0),
0056           dataTrackReader(0),
0057           audioSessionReader(0),
0058           cdrecordWriter(0),
0059           infFileWriter(0),
0060           cddb(0) {
0061     }
0062 
0063     bool canceled;
0064     bool error;
0065     bool readingSuccessful;
0066     bool running;
0067 
0068     int numSessions;
0069     bool doNotCloseLastSession;
0070 
0071     int doneCopies;
0072     int currentReadSession;
0073     int currentWrittenSession;
0074 
0075     K3b::Device::Toc toc;
0076     QByteArray cdTextRaw;
0077 
0078     K3b::ReadcdReader* readcdReader;
0079     K3b::DataTrackReader* dataTrackReader;
0080     K3b::AudioSessionReadingJob* audioSessionReader;
0081     K3b::CdrecordWriter* cdrecordWriter;
0082     K3b::InfFileWriter* infFileWriter;
0083 
0084     bool audioReaderRunning;
0085     bool dataReaderRunning;
0086     bool writerRunning;
0087 
0088     // image filenames, one for every track
0089     QStringList imageNames;
0090 
0091     // inf-filenames for writing audio tracks
0092     QStringList infNames;
0093 
0094     // indicates if we created a dir or not
0095     bool deleteTempDir;
0096 
0097     KCDDB::Client* cddb;
0098     KCDDB::CDInfo cddbInfo;
0099 
0100     bool haveCddb;
0101     bool haveCdText;
0102 
0103     QVector<bool> dataSessionProbablyTAORecorded;
0104 
0105     // used to determine progress
0106     QVector<long> sessionSizes;
0107     long overallSize;
0108 };
0109 
0110 
0111 K3b::CdCopyJob::CdCopyJob( K3b::JobHandler* hdl, QObject* parent )
0112     : K3b::BurnJob( hdl, parent ),
0113       m_simulate(false),
0114       m_copies(1),
0115       m_onlyCreateImages(false),
0116       m_onTheFly(true),
0117       m_ignoreDataReadErrors(false),
0118       m_ignoreAudioReadErrors(true),
0119       m_noCorrection(false),
0120       m_dataReadRetries(128),
0121       m_audioReadRetries(5),
0122       m_copyCdText(true),
0123       m_writingMode( K3b::WritingModeAuto )
0124 {
0125     d = new Private();
0126 }
0127 
0128 
0129 K3b::CdCopyJob::~CdCopyJob()
0130 {
0131     delete d->infFileWriter;
0132     delete d;
0133 }
0134 
0135 
0136 void K3b::CdCopyJob::start()
0137 {
0138     d->running = true;
0139     d->canceled = false;
0140     d->error = false;
0141     d->readingSuccessful = false;
0142     d->audioReaderRunning = d->dataReaderRunning = d->writerRunning = false;
0143     d->sessionSizes.clear();
0144     d->dataSessionProbablyTAORecorded.clear();
0145     d->deleteTempDir = false;
0146     d->haveCdText = false;
0147     d->haveCddb = false;
0148 
0149     if ( m_onlyCreateImages )
0150         m_onTheFly = false;
0151 
0152     jobStarted();
0153 
0154     emit newTask( i18n("Checking Source Medium") );
0155 
0156     emit burning(false);
0157     emit newSubTask( i18n("Waiting for source medium") );
0158 
0159     // wait for a source disk
0160     if( waitForMedium( m_readerDevice,
0161                        K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE,
0162                        K3b::Device::MEDIA_WRITABLE_CD|K3b::Device::MEDIA_CD_ROM ) == Device::MEDIA_UNKNOWN ) {
0163         finishJob( true, false );
0164         return;
0165     }
0166 
0167     emit newSubTask( i18n("Checking source medium") );
0168 
0169     // FIXME: read ISRCs and MCN
0170 
0171     connect( K3b::Device::mediaInfo( m_readerDevice ), SIGNAL(finished(K3b::Device::DeviceHandler*)),
0172              this, SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) );
0173 }
0174 
0175 
0176 void K3b::CdCopyJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh )
0177 {
0178     if( dh->success() ) {
0179         d->toc = dh->toc();
0180 
0181         //
0182         // for now we copy audio, pure data (aka 1 data track), cd-extra (2 session, audio and data),
0183         // and data multisession which one track per session.
0184         // Everything else will be rejected
0185         //
0186         bool canCopy = true;
0187         bool audio = false;
0188         d->numSessions = dh->diskInfo().numSessions();
0189         d->doNotCloseLastSession = (dh->diskInfo().diskState() == K3b::Device::STATE_INCOMPLETE);
0190         switch( dh->toc().contentType() ) {
0191         case K3b::Device::DATA:
0192             // check if every track is in it's own session
0193             // only then we copy the cd
0194             if( (int)dh->toc().count() != dh->diskInfo().numSessions() ) {
0195                 emit infoMessage( i18n("K3b does not copy CDs containing multiple data tracks."), MessageError );
0196                 canCopy = false;
0197             }
0198             else if( dh->diskInfo().numSessions() > 1 )
0199                 emit infoMessage( i18n("Copying Multisession Data CD."), MessageInfo );
0200             else
0201                 emit infoMessage( i18n("Copying Data CD."), MessageInfo );
0202             break;
0203 
0204         case K3b::Device::MIXED:
0205             audio = true;
0206             if( dh->diskInfo().numSessions() != 2 || d->toc[0].type() != K3b::Device::Track::TYPE_AUDIO ) {
0207                 emit infoMessage( i18n("K3b can only copy CD-Extra mixed mode CDs."), MessageError );
0208                 canCopy = false;
0209             }
0210             else
0211                 emit infoMessage( i18n("Copying Enhanced Audio CD (CD-Extra)."), MessageInfo );
0212             break;
0213 
0214         case K3b::Device::AUDIO:
0215             audio = true;
0216             emit infoMessage( i18n("Copying Audio CD."), MessageInfo );
0217             break;
0218 
0219         case K3b::Device::NONE:
0220         default:
0221             emit infoMessage( i18n("The source disk is empty."), MessageError );
0222             canCopy = false;
0223             break;
0224         }
0225 
0226         //
0227         // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain
0228         // zero data anyway. The problem is that I do not know of a valid method to determine if a track
0229         // was written in TAO (the control nibble does definitely not work, I never saw one which did not
0230         // equal 4).
0231         // So the solution for now is to simply try to read the last sector of a data track. If this is not
0232         // possible we assume it was written in TAO mode and reduce the length by 2 sectors
0233         //
0234         unsigned char buffer[2048];
0235         int i = 1;
0236         for( K3b::Device::Toc::iterator it = d->toc.begin(); it != d->toc.end(); ++it ) {
0237             if( (*it).type() == K3b::Device::Track::TYPE_DATA ) {
0238                 // we try twice just to be sure
0239                 if( m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) ||
0240                     m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) ) {
0241                     d->dataSessionProbablyTAORecorded.append(false);
0242                     qDebug() << "(K3b::CdCopyJob) track " << i << " probably DAO recorded.";
0243                 }
0244                 else {
0245                     d->dataSessionProbablyTAORecorded.append(true);
0246                     qDebug() << "(K3b::CdCopyJob) track " << i << " probably TAO recorded.";
0247                 }
0248             }
0249 
0250             ++i;
0251         }
0252 
0253 
0254         //
0255         // To copy mode2 data tracks we need cdrecord >= 2.01a12 which introduced the -xa1 and -xamix options
0256         //
0257         const K3b::ExternalBin* cdrecordBin = k3bcore->externalBinManager()->binObject("cdrecord");
0258         if( cdrecordBin && !cdrecordBin->hasFeature( "xamix" ) ) {
0259             for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) {
0260                 if( (*it).type() == K3b::Device::Track::TYPE_DATA &&
0261                     ( (*it).mode() == K3b::Device::Track::XA_FORM1 ||
0262                       (*it).mode() == K3b::Device::Track::XA_FORM2 ) ) {
0263                     emit infoMessage( i18n("K3b needs cdrecord 2.01a12 or newer to copy Mode2 data tracks."), MessageError );
0264                     finishJob( true, false );
0265                     return;
0266                 }
0267             }
0268         }
0269 
0270 
0271         //
0272         // It is not possible to create multisession cds in raw writing mode
0273         //
0274         if( d->numSessions > 1 && m_writingMode == K3b::WritingModeRaw ) {
0275             if( !questionYesNo( i18n("You will only be able to copy the first session in raw writing mode. "
0276                                      "Continue anyway?"),
0277                                 i18n("Multisession CD") ) ) {
0278                 finishJob( true, false );
0279                 return;
0280             }
0281             else {
0282                 emit infoMessage( i18n("Only copying first session."), MessageWarning );
0283                 // TODO: remove the second session from the progress stuff
0284             }
0285         }
0286 
0287 
0288         //
0289         // We already create the temp filenames here since we need them to check the free space
0290         //
0291         if( !m_onTheFly || m_onlyCreateImages ) {
0292             if( !prepareImageFiles() ) {
0293                 finishJob( false, true );
0294                 return;
0295             }
0296 
0297             //
0298             // check free temp space
0299             //
0300             KIO::filesize_t imageSpaceNeeded = 0;
0301             for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) {
0302                 if( (*it).type() == K3b::Device::Track::TYPE_AUDIO )
0303                     imageSpaceNeeded += (*it).length().audioBytes() + 44;
0304                 else
0305                     imageSpaceNeeded += (*it).length().mode1Bytes();
0306             }
0307 
0308             unsigned long avail, size;
0309             QString pathToTest = m_tempPath.left( m_tempPath.lastIndexOf( '/' ) );
0310             if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) {
0311                 emit infoMessage( i18n("Unable to determine free space in temporary folder '%1'.",pathToTest), MessageError );
0312                 d->error = true;
0313                 canCopy = false;
0314             }
0315             else {
0316                 if( avail < imageSpaceNeeded/1024 ) {
0317                     emit infoMessage( i18n("Not enough space left in temporary folder."), MessageError );
0318                     d->error = true;
0319                     canCopy = false;
0320                 }
0321             }
0322         }
0323 
0324         if( canCopy ) {
0325             if( K3b::isMounted( m_readerDevice ) ) {
0326                 emit infoMessage( i18n("Unmounting source medium"), MessageInfo );
0327                 K3b::unmount( m_readerDevice );
0328             }
0329 
0330             d->overallSize = 0;
0331 
0332             // now create some progress helper values
0333             for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) {
0334                 d->overallSize += (*it).length().lba();
0335                 if( d->sessionSizes.isEmpty() || (*it).type() == K3b::Device::Track::TYPE_DATA )
0336                     d->sessionSizes.append( (*it).length().lba() );
0337                 else
0338                     d->sessionSizes[0] += (*it).length().lba();
0339             }
0340 
0341             if( audio && !m_onlyCreateImages ) {
0342                 if( m_copyCdText )
0343                     searchCdText();
0344                 else
0345                     queryCddb();
0346             }
0347             else
0348                 startCopy();
0349         }
0350         else {
0351             finishJob( false, true );
0352         }
0353     }
0354     else {
0355         emit infoMessage( i18n("Unable to read Table of contents"), MessageError );
0356         finishJob( false, true );
0357     }
0358 }
0359 
0360 
0361 void K3b::CdCopyJob::searchCdText()
0362 {
0363     emit newSubTask( i18n("Searching CD-Text") );
0364 
0365     connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CommandCdTextRaw, m_readerDevice ),
0366              SIGNAL(finished(K3b::Device::DeviceHandler*)),
0367              this,
0368              SLOT(slotCdTextReady(K3b::Device::DeviceHandler*)) );
0369 }
0370 
0371 
0372 void K3b::CdCopyJob::slotCdTextReady( K3b::Device::DeviceHandler* dh )
0373 {
0374     if( dh->success() ) {
0375         if( K3b::Device::CdText::checkCrc( dh->cdTextRaw() ) ) {
0376             K3b::Device::CdText cdt( dh->cdTextRaw() );
0377             emit infoMessage( i18n("Found CD-Text (%1 - %2).",cdt.performer(),cdt.title()), MessageSuccess );
0378             d->haveCdText = true;
0379             d->cdTextRaw = dh->cdTextRaw();
0380         }
0381         else {
0382             emit infoMessage( i18n("Found corrupted CD-Text. Ignoring it."), MessageWarning );
0383             d->haveCdText = false;
0384         }
0385     }
0386     else {
0387         emit infoMessage( i18n("No CD-Text found."), MessageInfo );
0388 
0389         d->haveCdText = false;
0390     }
0391 
0392     queryCddb();
0393 }
0394 
0395 
0396 void K3b::CdCopyJob::queryCddb()
0397 {
0398     emit newSubTask( i18n("Querying CDDB") );
0399 
0400     d->haveCddb = false;
0401 
0402     if( !d->cddb ) {
0403         d->cddb = new KCDDB::Client();
0404         d->cddb->setBlockingMode( false );
0405         connect( d->cddb, SIGNAL(finished(KCDDB::Result)),
0406                  this, SLOT(slotCddbQueryFinished(KCDDB::Result)) );
0407     }
0408 
0409     d->cddb->config().load();
0410     d->cddb->lookup( K3b::CDDB::createTrackOffsetList( d->toc ) );
0411 }
0412 
0413 
0414 void K3b::CdCopyJob::slotCddbQueryFinished( KCDDB::Result result )
0415 {
0416     if( result == KCDDB::Success ) {
0417         d->cddbInfo = d->cddb->lookupResponse().first();
0418         d->haveCddb = true;
0419         emit infoMessage( i18n("Found CDDB entry (%1 - %2).",
0420                                d->cddbInfo.get( KCDDB::Artist ).toString(),
0421                                d->cddbInfo.get( KCDDB::Title ).toString() ),
0422                           MessageSuccess );
0423 
0424         // save the entry locally
0425         d->cddb->store( d->cddbInfo, K3b::CDDB::createTrackOffsetList( d->toc ) );
0426     }
0427     else if ( result == KCDDB::MultipleRecordFound ) {
0428         KCDDB::CDInfoList results = d->cddb->lookupResponse();
0429         int i = K3b::CDDB::MultiEntriesDialog::selectCddbEntry( results, qApp->activeWindow() );
0430         if ( i >= 0 ) {
0431             d->haveCddb = true;
0432             d->cddbInfo = results[i];
0433 
0434             // save the entry locally
0435             d->cddb->store( d->cddbInfo, K3b::CDDB::createTrackOffsetList( d->toc ) );
0436         }
0437         else {
0438             d->haveCddb = false;
0439         }
0440     }
0441     else if( result == KCDDB::NoRecordFound ) {
0442         emit infoMessage( i18n("No CDDB entry found."), MessageWarning );
0443     }
0444     else {
0445         emit infoMessage( i18n("CDDB error (%1).",
0446                                KCDDB::resultToString( result ) ),
0447                           MessageError );
0448     }
0449 
0450     startCopy();
0451 }
0452 
0453 
0454 void K3b::CdCopyJob::startCopy()
0455 {
0456     d->currentWrittenSession = d->currentReadSession = 1;
0457     d->doneCopies = 0;
0458 
0459     if ( d->haveCdText && d->haveCddb ) {
0460         K3b::Device::CdText cdt( d->cdTextRaw );
0461         if ( !questionYesNo( i18n( "Found CD-Text (%1 - %2) and CDDB (%3 - %4) entries. "
0462                                    "Which one should be used to generate the CD-Text on the new CD?",
0463                                    cdt.performer(),
0464                                    cdt.title(),
0465                                    d->cddbInfo.get( KCDDB::Artist ).toString(),
0466                                    d->cddbInfo.get( KCDDB::Title ).toString() ),
0467                              i18n( "CD-Text" ),
0468                              KGuiItem( i18n( "Use CD-Text data" ) ),
0469                              KGuiItem( i18n( "Use CDDB entry" ) ) ) ) {
0470             d->haveCdText = false;
0471         }
0472     }
0473 
0474     if( m_onTheFly && !m_onlyCreateImages ) {
0475         emit newSubTask( i18n("Preparing write process...") );
0476 
0477         if( writeNextSession() )
0478             readNextSession();
0479         else {
0480             finishJob( d->canceled, d->error );
0481         }
0482     }
0483     else
0484         readNextSession();
0485 }
0486 
0487 
0488 void K3b::CdCopyJob::cancel()
0489 {
0490     d->canceled = true;
0491 
0492     if( d->writerRunning ) {
0493         //
0494         // we will handle cleanup in slotWriterFinished()
0495         // if we are writing onthefly the reader won't be able to write
0496         // anymore and will finish unsuccessfully, too
0497         //
0498         d->cdrecordWriter->cancel();
0499     }
0500     else if( d->audioReaderRunning )
0501         d->audioSessionReader->cancel();
0502     else if( d->dataReaderRunning )
0503         //    d->readcdReader->cancel();
0504         d->dataTrackReader->cancel();
0505 }
0506 
0507 
0508 bool K3b::CdCopyJob::prepareImageFiles()
0509 {
0510     qDebug() << "(K3b::CdCopyJob) prepareImageFiles()";
0511 
0512     d->imageNames.clear();
0513     d->infNames.clear();
0514     d->deleteTempDir = false;
0515 
0516     QFileInfo fi( m_tempPath );
0517 
0518     if( d->toc.count() > 1 || d->toc.contentType() == K3b::Device::AUDIO ) {
0519         // create a directory which contains all the images and inf and stuff
0520         // and save it in some cool structure
0521 
0522         bool tempDirReady = false;
0523         if( !fi.isDir() ) {
0524             if( QFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) {
0525                 if( !QFile::exists( m_tempPath ) ) {
0526                     QDir dir( m_tempPath.section( '/', 0, -2 ) );
0527                     dir.mkdir( m_tempPath.section( '/', -1 ) );
0528                     tempDirReady = true;
0529                 }
0530                 else
0531                     m_tempPath = m_tempPath.section( '/', 0, -2 );
0532             }
0533             else {
0534                 emit infoMessage( i18n("Specified an unusable temporary path. Using default."), MessageWarning );
0535                 m_tempPath = K3b::defaultTempPath();
0536             }
0537         }
0538 
0539         // create temp dir
0540         if( !tempDirReady ) {
0541             QDir dir( m_tempPath );
0542             m_tempPath = K3b::findUniqueFilePrefix( "k3bCdCopy", m_tempPath );
0543             qDebug() << "(K3b::CdCopyJob) creating temp dir: " << m_tempPath;
0544             if( !dir.mkdir( m_tempPath ) ) {
0545                 emit infoMessage( i18n("Unable to create temporary folder '%1'.",m_tempPath), MessageError );
0546                 return false;
0547             }
0548             d->deleteTempDir = true;
0549         }
0550 
0551         m_tempPath = K3b::prepareDir( m_tempPath );
0552         emit infoMessage( i18n("Using temporary folder %1.",m_tempPath), MessageInfo );
0553 
0554         // create temp filenames
0555         int i = 1;
0556         for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) {
0557             if( (*it).type() == K3b::Device::Track::TYPE_AUDIO ) {
0558                 d->imageNames.append( m_tempPath + QString("Track%1.wav").arg(QString::number(i).rightJustified(2, '0')) );
0559                 d->infNames.append( m_tempPath + QString("Track%1.inf").arg(QString::number(i).rightJustified(2, '0')) );
0560             }
0561             else
0562                 d->imageNames.append( m_tempPath + QString("Track%1.iso").arg(QString::number(i).rightJustified(2, '0')) );
0563             ++i;
0564         }
0565 
0566         qDebug() << "(K3b::CdCopyJob) created image filenames:";
0567         for( int i = 0; i < d->imageNames.count(); ++i )
0568             qDebug() << "(K3b::CdCopyJob) " << d->imageNames[i];
0569 
0570         return true;
0571     }
0572     else {
0573         // we only need a single image file
0574         if( !fi.isFile() ||
0575             questionYesNo( i18n("Do you want to overwrite %1?",m_tempPath),
0576                            i18n("File Exists") ) ) {
0577             if( fi.isDir() )
0578                 m_tempPath = K3b::findTempFile( "iso", m_tempPath );
0579             else if( !QFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) {
0580                 emit infoMessage( i18n("Specified an unusable temporary path. Using default."), MessageWarning );
0581                 m_tempPath = K3b::findTempFile( "iso" );
0582             }
0583             // else the user specified a file in an existing dir
0584 
0585             emit infoMessage( i18n("Writing image file to %1.",m_tempPath), MessageInfo );
0586         }
0587         else
0588             return false;
0589 
0590         d->imageNames.append( m_tempPath );
0591 
0592         return true;
0593     }
0594 }
0595 
0596 
0597 void K3b::CdCopyJob::readNextSession()
0598 {
0599     if( !m_onTheFly || m_onlyCreateImages ) {
0600         if( d->numSessions > 1 )
0601             emit newTask( i18n("Reading Session %1",d->currentReadSession) );
0602         else
0603             emit newTask( i18n("Reading Source Medium") );
0604 
0605         if( d->currentReadSession == 1 )
0606             emit newSubTask( i18n("Reading track %1 of %2",QString::number(1),d->toc.count()) );
0607     }
0608 
0609     // there is only one situation where we need the audiosessionreader:
0610     // if the first session is an audio session. That means the first track
0611     // is an audio track
0612     if( d->currentReadSession == 1 && d->toc[0].type() == K3b::Device::Track::TYPE_AUDIO ) {
0613         if( !d->audioSessionReader ) {
0614             d->audioSessionReader = new K3b::AudioSessionReadingJob( this, this );
0615             connect( d->audioSessionReader, SIGNAL(nextTrack(int,int)),
0616                      this, SLOT(slotReadingNextTrack(int,int)) );
0617             connectSubJob( d->audioSessionReader,
0618                            SLOT(slotSessionReaderFinished(bool)),
0619                            K3b::Job::DEFAULT_SIGNAL_CONNECTION,
0620                            K3b::Job::DEFAULT_SIGNAL_CONNECTION,
0621                            SLOT(slotReaderProgress(int)),
0622                            SLOT(slotReaderSubProgress(int)) );
0623         }
0624 
0625         d->audioSessionReader->setDevice( m_readerDevice );
0626         d->audioSessionReader->setToc( d->toc );
0627         d->audioSessionReader->setParanoiaMode( m_paranoiaMode );
0628         d->audioSessionReader->setReadRetries( m_audioReadRetries );
0629         d->audioSessionReader->setNeverSkip( !m_ignoreAudioReadErrors );
0630         if( m_onTheFly )
0631             d->audioSessionReader->writeTo( d->cdrecordWriter->ioDevice() );
0632         else
0633             d->audioSessionReader->setImageNames( d->imageNames );  // the audio tracks are always the first tracks
0634 
0635         d->audioReaderRunning = true;
0636         d->audioSessionReader->start();
0637     }
0638     else {
0639         if( !d->dataTrackReader ) {
0640             d->dataTrackReader = new K3b::DataTrackReader( this, this );
0641             connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) );
0642             connect( d->dataTrackReader, SIGNAL(processedSize(int,int)), this, SLOT(slotReaderProcessedSize(int,int)) );
0643             connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotSessionReaderFinished(bool)) );
0644             connect( d->dataTrackReader, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
0645             connect( d->dataTrackReader, SIGNAL(debuggingOutput(QString,QString)),
0646                      this, SIGNAL(debuggingOutput(QString,QString)) );
0647         }
0648 
0649         d->dataTrackReader->setDevice( m_readerDevice );
0650         d->dataTrackReader->setIgnoreErrors( m_ignoreDataReadErrors );
0651         d->dataTrackReader->setNoCorrection( m_noCorrection );
0652         d->dataTrackReader->setRetries( m_dataReadRetries );
0653         if( m_onlyCreateImages )
0654             d->dataTrackReader->setSectorSize( K3b::DataTrackReader::MODE1 );
0655         else
0656             d->dataTrackReader->setSectorSize( K3b::DataTrackReader::AUTO );
0657 
0658         K3b::Device::Track* track = 0;
0659         int dataTrackIndex = 0;
0660         if( d->toc.contentType() == K3b::Device::MIXED ) {
0661             track = &d->toc[d->toc.count()-1];
0662             dataTrackIndex = 0;
0663         }
0664         else {
0665             track = &d->toc[d->currentReadSession-1]; // only one track per session
0666             dataTrackIndex = d->currentReadSession-1;
0667         }
0668 
0669         // HACK: if the track is TAO recorded cut the two run-out sectors
0670         if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex &&
0671             d->dataSessionProbablyTAORecorded[dataTrackIndex] )
0672             d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() - 2 );
0673         else
0674             d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() );
0675 
0676         int trackNum = d->currentReadSession;
0677         if( d->toc.contentType() == K3b::Device::MIXED )
0678             trackNum = d->toc.count();
0679 
0680         if( m_onTheFly )
0681             d->dataTrackReader->writeTo( d->cdrecordWriter->ioDevice() );
0682         else
0683             d->dataTrackReader->setImagePath( d->imageNames[trackNum-1] );
0684 
0685         d->dataReaderRunning = true;
0686         if( !m_onTheFly || m_onlyCreateImages )
0687             slotReadingNextTrack( 1, 1 );
0688 
0689         d->dataTrackReader->start();
0690     }
0691 }
0692 
0693 
0694 bool K3b::CdCopyJob::writeNextSession()
0695 {
0696     // we emit our own task since the cdrecord task is way too simple
0697     if( d->numSessions > 1 ) {
0698         if( m_simulate )
0699             emit newTask( i18n("Simulating Session %1",d->currentWrittenSession) );
0700         else if( m_copies > 1 )
0701             emit newTask( i18n("Writing Copy %1 (Session %2)",d->doneCopies+1,d->currentWrittenSession) );
0702         else
0703             emit newTask( i18n("Writing Copy (Session %1)",d->currentWrittenSession) );
0704     }
0705     else {
0706         if( m_simulate )
0707             emit newTask( i18n("Simulating") );
0708         else if( m_copies > 1 )
0709             emit newTask( i18n("Writing Copy %1",d->doneCopies+1) );
0710         else
0711             emit newTask( i18n("Writing Copy") );
0712     }
0713 
0714     if ( d->currentWrittenSession == 1 ) {
0715         emit newSubTask( i18n("Waiting for media") );
0716 
0717         if( waitForMedium( m_writerDevice,
0718                           K3b::Device::STATE_EMPTY,
0719                           K3b::Device::MEDIA_WRITABLE_CD ) == Device::MEDIA_UNKNOWN ) {
0720             finishJob( true, false );
0721             return false;
0722         }
0723     }
0724 
0725     if( !d->cdrecordWriter ) {
0726         d->cdrecordWriter = new K3b::CdrecordWriter( m_writerDevice, this, this );
0727         connect( d->cdrecordWriter, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
0728         connect( d->cdrecordWriter, SIGNAL(percent(int)), this, SLOT(slotWriterProgress(int)) );
0729         connect( d->cdrecordWriter, SIGNAL(processedSize(int,int)), this, SIGNAL(processedSize(int,int)) );
0730         connect( d->cdrecordWriter, SIGNAL(subPercent(int)), this, SIGNAL(subPercent(int)) );
0731         connect( d->cdrecordWriter, SIGNAL(processedSubSize(int,int)), this, SIGNAL(processedSubSize(int,int)) );
0732         connect( d->cdrecordWriter, SIGNAL(nextTrack(int,int)), this, SLOT(slotWritingNextTrack(int,int)) );
0733         connect( d->cdrecordWriter, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) );
0734         connect( d->cdrecordWriter, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) );
0735         connect( d->cdrecordWriter, SIGNAL(writeSpeed(int,K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int,K3b::Device::SpeedMultiplicator)) );
0736         connect( d->cdrecordWriter, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) );
0737         //    connect( d->cdrecordWriter, SIGNAL(newTask(QString)), this, SIGNAL(newTask(QString)) );
0738         connect( d->cdrecordWriter, SIGNAL(newSubTask(QString)), this, SIGNAL(newSubTask(QString)) );
0739         connect( d->cdrecordWriter, SIGNAL(debuggingOutput(QString,QString)),
0740                  this, SIGNAL(debuggingOutput(QString,QString)) );
0741     }
0742 
0743     d->cdrecordWriter->setBurnDevice( m_writerDevice );
0744     d->cdrecordWriter->clearArguments();
0745     d->cdrecordWriter->setSimulate( m_simulate );
0746     d->cdrecordWriter->setBurnSpeed( m_speed );
0747 
0748 
0749     // create the cdrecord arguments
0750     if( d->currentWrittenSession == 1 && d->toc[0].type() == K3b::Device::Track::TYPE_AUDIO ) {
0751         //
0752         // Audio session
0753         //
0754 
0755 
0756         if( !d->infFileWriter )
0757             d->infFileWriter = new K3b::InfFileWriter();
0758 
0759         //
0760         // create the inf files if not already done
0761         //
0762         if( d->infNames.isEmpty() || !QFile::exists( d->infNames[0] ) ) {
0763 
0764             int trackNumber = 1;
0765 
0766             for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) {
0767                 const K3b::Device::Track& track = *it;
0768 
0769                 if( track.type() == K3b::Device::Track::TYPE_DATA )
0770                     break;
0771 
0772                 d->infFileWriter->setTrack( track );
0773                 d->infFileWriter->setTrackNumber( trackNumber );
0774 
0775                 if( d->haveCddb ) {
0776                     d->infFileWriter->setTrackTitle( d->cddbInfo.track( trackNumber-1 ).get( KCDDB::Title ).toString() );
0777                     d->infFileWriter->setTrackPerformer( d->cddbInfo.track( trackNumber-1 ).get( KCDDB::Artist ).toString() );
0778                     d->infFileWriter->setTrackMessage( d->cddbInfo.track( trackNumber-1 ).get( KCDDB::Comment ).toString() );
0779 
0780                     d->infFileWriter->setAlbumTitle( d->cddbInfo.get( KCDDB::Title ).toString() );
0781                     d->infFileWriter->setAlbumPerformer( d->cddbInfo.get( KCDDB::Artist ).toString() );
0782                 }
0783 
0784                 if( m_onTheFly ) {
0785 
0786                     d->infFileWriter->setBigEndian( true );
0787 
0788                     // we let KTempFile choose a temp file but delete it on our own
0789                     // the same way we delete them when writing with images
0790                     // It is important that the files have the ending inf because
0791                     // cdrecord only checks this
0792                     QTemporaryFile tmp( "XXXXXX.inf" );
0793                     tmp.setAutoRemove( false );
0794                     tmp.open();
0795                     d->infNames.append( tmp.fileName() );
0796                     QTextStream stream( &tmp );
0797                     bool success = d->infFileWriter->save( stream );
0798                     tmp.close();
0799                     if( !success )
0800                         return false;
0801                 }
0802                 else {
0803                     d->infFileWriter->setBigEndian( false );
0804 
0805                     if( !d->infFileWriter->save( d->infNames[trackNumber-1] ) )
0806                         return false;
0807                 }
0808 
0809                 ++trackNumber;
0810             }
0811         }
0812 
0813         //
0814         // the inf files are ready and named correctly when writing with images
0815         //
0816         K3b::WritingMode usedWritingMode = m_writingMode;
0817         if( usedWritingMode == K3b::WritingModeAuto ) {
0818             //
0819             // there are a lot of writers out there which produce coasters
0820             // in dao mode if the CD contains pregaps of length 0 (or maybe already != 2 secs?)
0821             //
0822             bool zeroPregap = false;
0823             if( d->numSessions == 1 ) {
0824                 for( K3b::Device::Toc::const_iterator it = d->toc.constBegin(); it != d->toc.constEnd(); ++it ) {
0825                     const K3b::Device::Track& track = *it;
0826                     if( track.index0() == 0 ) {
0827                         ++it;
0828                         if( it != d->toc.constEnd() )
0829                             zeroPregap = true;
0830                         --it;
0831                     }
0832                 }
0833             }
0834 
0835             if( zeroPregap && m_writerDevice->supportsRawWriting() ) {
0836                 if( d->numSessions == 1 )
0837                     usedWritingMode = K3b::WritingModeRaw;
0838                 else
0839                     usedWritingMode = K3b::WritingModeTao;
0840             }
0841             else if( m_writerDevice->dao() )
0842                 usedWritingMode = K3b::WritingModeSao;
0843             else if( m_writerDevice->supportsRawWriting() )
0844                 usedWritingMode = K3b::WritingModeRaw;
0845             else
0846                 usedWritingMode = K3b::WritingModeTao;
0847         }
0848         d->cdrecordWriter->setWritingMode( usedWritingMode  );
0849 
0850         d->cdrecordWriter->setMulti( d->numSessions > 1 );
0851 
0852         if( d->haveCddb || d->haveCdText ) {
0853             if( usedWritingMode == K3b::WritingModeTao ) {
0854                 emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), MessageWarning );
0855             }
0856             else if( d->haveCdText ) {
0857                 // use the raw CDTEXT data
0858                 d->cdrecordWriter->setRawCdText( d->cdTextRaw );
0859             }
0860             else {
0861                 // make sure the writer job does not create raw cdtext
0862                 d->cdrecordWriter->setRawCdText( QByteArray() );
0863                 // cdrecord will use the cdtext data in the inf files
0864                 d->cdrecordWriter->addArgument( "-text" );
0865             }
0866         }
0867 
0868         d->cdrecordWriter->addArgument( "-useinfo" );
0869 
0870         //
0871         // add all the audio tracks
0872         //
0873         d->cdrecordWriter->addArgument( "-audio" )->addArgument( "-shorttrack" );
0874 
0875         for( int i = 0; i < d->infNames.count(); ++i ) {
0876             if( m_onTheFly )
0877                 d->cdrecordWriter->addArgument( d->infNames[i] );
0878             else
0879                 d->cdrecordWriter->addArgument( d->imageNames[i] );
0880         }
0881     }
0882     else {
0883         //
0884         // Data Session
0885         //
0886         K3b::Device::Track* track = 0;
0887         int dataTrackIndex = 0;
0888         if( d->toc.contentType() == K3b::Device::MIXED ) {
0889             track = &d->toc[d->toc.count()-1];
0890             dataTrackIndex = 0;
0891         }
0892         else {
0893             track = &d->toc[d->currentWrittenSession-1];
0894             dataTrackIndex = d->currentWrittenSession-1;
0895         }
0896 
0897         bool multi = d->doNotCloseLastSession || (d->numSessions > 1 && d->currentWrittenSession < d->toc.count());
0898         K3b::WritingMode usedWritingMode = m_writingMode;
0899         if( usedWritingMode == K3b::WritingModeAuto ) {
0900             // at least the NEC3540a does write 2056 byte sectors only in tao mode. Same for LG4040b
0901             // since writing data tracks in TAO mode is no loss let's default to TAO in the case of 2056 byte
0902             // sectors (which is when writing xa form1 sectors here)
0903             if( m_writerDevice->dao() &&
0904                 d->toc.count() == 1 &&
0905                 !multi &&
0906                 track->mode() == K3b::Device::Track::MODE1 )
0907                 usedWritingMode = K3b::WritingModeSao;
0908             else
0909                 usedWritingMode = K3b::WritingModeTao;
0910         }
0911         d->cdrecordWriter->setWritingMode( usedWritingMode );
0912 
0913         //
0914         // all but the last session of a multisession disk are written in multi mode
0915         // and every data track has it's own session which we forced above
0916         //
0917         d->cdrecordWriter->setMulti( multi );
0918 
0919         // just to let the reader init
0920         if( m_onTheFly )
0921             d->cdrecordWriter->addArgument( "-waiti" );
0922 
0923         if( track->mode() == K3b::Device::Track::MODE1 )
0924             d->cdrecordWriter->addArgument( "-data" );
0925         else if( track->mode() == K3b::Device::Track::XA_FORM1 )
0926             d->cdrecordWriter->addArgument( "-xa1" );
0927         else
0928             d->cdrecordWriter->addArgument( "-xamix" );
0929 
0930         if( m_onTheFly ) {
0931             // HACK: if the track is TAO recorded cut the two run-out sectors
0932             unsigned long trackLen = track->length().lba();
0933             if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex &&
0934                 d->dataSessionProbablyTAORecorded[dataTrackIndex] )
0935                 trackLen -= 2;
0936 
0937             if( track->mode() == K3b::Device::Track::MODE1 )
0938                 trackLen = trackLen * 2048;
0939             else if( track->mode() == K3b::Device::Track::XA_FORM1 )
0940                 trackLen = trackLen * 2056; // see k3bdatatrackreader.h
0941             else
0942                 trackLen = trackLen * 2332; // see k3bdatatrackreader.h
0943             d->cdrecordWriter->addArgument( QString("-tsize=%1").arg(trackLen) )->addArgument("-");
0944         }
0945         else if( d->toc.contentType() == K3b::Device::MIXED )
0946             d->cdrecordWriter->addArgument( d->imageNames[d->toc.count()-1] );
0947         else
0948             d->cdrecordWriter->addArgument( d->imageNames[d->currentWrittenSession-1] );
0949 
0950         // clear cd text from previous sessions
0951         d->cdrecordWriter->setRawCdText( QByteArray() );
0952     }
0953 
0954 
0955     //
0956     // Finally start the writer
0957     //
0958     emit burning(true);
0959     d->writerRunning = true;
0960     d->cdrecordWriter->start();
0961 
0962     return true;
0963 }
0964 
0965 
0966 // both the readcdreader and the audiosessionreader are connected to this slot
0967 void K3b::CdCopyJob::slotSessionReaderFinished( bool success )
0968 {
0969     d->audioReaderRunning = d->dataReaderRunning = false;
0970 
0971     if( success ) {
0972         if( d->numSessions > 1 )
0973             emit infoMessage( i18n("Successfully read session %1.",d->currentReadSession), MessageSuccess );
0974         else
0975             emit infoMessage( i18n("Successfully read source disk."), MessageSuccess );
0976 
0977         if( !m_onTheFly ) {
0978             if( d->numSessions > d->currentReadSession ) {
0979                 d->currentReadSession++;
0980                 readNextSession();
0981             }
0982             else {
0983                 d->readingSuccessful = true;
0984                 if( !m_onlyCreateImages ) {
0985                     if( m_readerDevice == m_writerDevice ) {
0986                         // eject the media (we do this blocking to know if it worked
0987                         // because if it did not it might happen that k3b overwrites a CD-RW
0988                         // source)
0989                         if( !K3b::eject( m_readerDevice ) ) {
0990                             blockingInformation( i18n("K3b was unable to eject the source disk. Please do so manually.") );
0991                         }
0992                     }
0993 
0994                     if( !writeNextSession() ) {
0995                         // nothing is running here...
0996                         finishJob( d->canceled, d->error );
0997                     }
0998                 }
0999                 else {
1000                     finishJob( false, false );
1001                 }
1002             }
1003         }
1004     }
1005     else {
1006         if( !d->canceled ) {
1007             emit infoMessage( i18n("Error while reading session %1.",d->currentReadSession), MessageError );
1008             if( m_onTheFly )
1009                 d->cdrecordWriter->setSourceUnreadable(true);
1010         }
1011 
1012         finishJob( d->canceled, !d->canceled );
1013     }
1014 }
1015 
1016 
1017 void K3b::CdCopyJob::slotWriterFinished( bool success )
1018 {
1019     emit burning(false);
1020 
1021     d->writerRunning = false;
1022 
1023     if( success ) {
1024         //
1025         // if this was the last written session we need to reset d->currentWrittenSession
1026         // and start a new writing if more copies are wanted
1027         //
1028 
1029         if( d->currentWrittenSession < d->numSessions ) {
1030             d->currentWrittenSession++;
1031             d->currentReadSession++;
1032 
1033             // many drives need to reload the medium to return to a proper state
1034             if ( m_writerDevice->diskInfo().numSessions() < ( int )d->currentWrittenSession ) {
1035                 emit infoMessage( i18n( "Need to reload medium to return to proper state." ), MessageInfo );
1036                 emit newSubTask( i18n("Reloading the medium") );
1037                 connect( K3b::Device::reload( m_writerDevice ), SIGNAL(finished(K3b::Device::DeviceHandler*)),
1038                          this, SLOT(slotMediaReloadedForNextSession(K3b::Device::DeviceHandler*)) );
1039             }
1040 
1041             if( !writeNextSession() ) {
1042                 // nothing is running here...
1043                 finishJob( d->canceled, d->error );
1044             }
1045             else if( m_onTheFly )
1046                 readNextSession();
1047         }
1048         else {
1049             d->doneCopies++;
1050 
1051             if( !m_simulate && d->doneCopies < m_copies ) {
1052                 // start next copy
1053                 if( !K3b::eject( m_writerDevice ) ) {
1054                     blockingInformation( i18n("K3b was unable to eject the written disk. Please do so manually.") );
1055                 }
1056 
1057                 d->currentWrittenSession = 1;
1058                 d->currentReadSession = 1;
1059                 if( writeNextSession() ) {
1060                     if( m_onTheFly )
1061                         readNextSession();
1062                 }
1063                 else {
1064                     // nothing running here...
1065                     finishJob( d->canceled, d->error );
1066                 }
1067             }
1068             else {
1069                 if ( k3bcore->globalSettings()->ejectMedia() ) {
1070                     K3b::Device::eject( m_writerDevice );
1071                 }
1072                 finishJob( false, false );
1073             }
1074         }
1075     }
1076     else {
1077         //
1078         // If we are writing on the fly the reader will also stop when it is not able to write anymore
1079         // The error handling will be done only here in that case
1080         //
1081 
1082         // the K3b::CdrecordWriter emitted an error message
1083 
1084         finishJob( d->canceled, !d->canceled );
1085     }
1086 }
1087 
1088 
1089 void K3b::CdCopyJob::slotMediaReloadedForNextSession( K3b::Device::DeviceHandler* dh )
1090 {
1091     if( !dh->success() )
1092         blockingInformation( i18n("Please reload the medium and press 'OK'"),
1093                              i18n("Failed to reload the medium") );
1094 
1095     if( !writeNextSession() ) {
1096         // nothing is running here...
1097         finishJob( d->canceled, d->error );
1098     }
1099     else if( m_onTheFly )
1100         readNextSession();
1101 }
1102 
1103 
1104 void K3b::CdCopyJob::cleanup()
1105 {
1106     if( m_onTheFly || !m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful) ) {
1107         emit infoMessage( i18n("Removing temporary files."), MessageInfo );
1108         for( QStringList::iterator it = d->infNames.begin(); it != d->infNames.end(); ++it )
1109             QFile::remove( *it );
1110     }
1111 
1112     if( !m_onTheFly && (!m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful)) ) {
1113         emit infoMessage( i18n("Removing image files."), MessageInfo );
1114         for( QStringList::iterator it = d->imageNames.begin(); it != d->imageNames.end(); ++it )
1115             QFile::remove( *it );
1116 
1117         // remove the tempdir created in prepareImageFiles()
1118         if( d->deleteTempDir ) {
1119             KIO::del( QUrl::fromLocalFile( m_tempPath ), KIO::HideProgressInfo )->exec();
1120             d->deleteTempDir = false;
1121         }
1122     }
1123 }
1124 
1125 
1126 void K3b::CdCopyJob::slotReaderProgress( int p )
1127 {
1128     if( !m_onTheFly || m_onlyCreateImages ) {
1129         int bigParts = ( m_onlyCreateImages ? 1 : (m_simulate ? 2 : m_copies + 1 ) );
1130         double done = (double)p * (double)d->sessionSizes[d->currentReadSession-1] / 100.0;
1131         for( int i = 0; i < d->currentReadSession-1; ++i )
1132             done += (double)d->sessionSizes[i];
1133         emit percent( (int)(100.0*done/(double)d->overallSize/(double)bigParts) );
1134 
1135         if( d->dataReaderRunning )
1136             emit subPercent(p);
1137     }
1138 }
1139 
1140 
1141 void K3b::CdCopyJob::slotReaderSubProgress( int p )
1142 {
1143     // only if reading an audiosession
1144     if( !m_onTheFly || m_onlyCreateImages ) {
1145         emit subPercent( p );
1146     }
1147 }
1148 
1149 
1150 void K3b::CdCopyJob::slotReaderProcessedSize( int p, int pp )
1151 {
1152     if( !m_onTheFly )
1153         emit processedSubSize( p, pp );
1154 }
1155 
1156 
1157 void K3b::CdCopyJob::slotWriterProgress( int p )
1158 {
1159     int bigParts = ( m_simulate ? 1 : m_copies ) + ( m_onTheFly ? 0 : 1 );
1160     long done = ( m_onTheFly ? d->doneCopies : d->doneCopies+1 ) * d->overallSize
1161                 + (p * d->sessionSizes[d->currentWrittenSession-1] / 100);
1162     for( int i = 0; i < d->currentWrittenSession-1; ++i )
1163         done += d->sessionSizes[i];
1164     emit percent( 100*done/d->overallSize/bigParts );
1165 }
1166 
1167 
1168 void K3b::CdCopyJob::slotWritingNextTrack( int t, int tt )
1169 {
1170     if( d->toc.contentType() == K3b::Device::MIXED ) {
1171         if( d->currentWrittenSession == 1 )
1172             emit newSubTask( i18n("Writing track %1 of %2",t,d->toc.count()) );
1173         else
1174             emit newSubTask( i18n("Writing track %1 of %2",d->toc.count(),d->toc.count()) );
1175     }
1176     else if( d->numSessions > 1 )
1177         emit newSubTask( i18n("Writing track %1 of %2",d->currentWrittenSession,d->toc.count()) );
1178     else
1179         emit newSubTask( i18n("Writing track %1 of %2",t,tt) );
1180 }
1181 
1182 
1183 void K3b::CdCopyJob::slotReadingNextTrack( int t, int )
1184 {
1185     if( !m_onTheFly || m_onlyCreateImages ) {
1186         int track = t;
1187         if( d->audioReaderRunning )
1188             track = t;
1189         else if( d->toc.contentType() == K3b::Device::MIXED )
1190             track = d->toc.count();
1191         else
1192             track = d->currentReadSession;
1193 
1194         emit newSubTask( i18n("Reading track %1 of %2",track,d->toc.count()) );
1195     }
1196 }
1197 
1198 
1199 QString K3b::CdCopyJob::jobDescription() const
1200 {
1201     if( m_onlyCreateImages ) {
1202         return i18n("Creating CD Image");
1203     }
1204     else if( m_simulate ) {
1205         if( m_onTheFly )
1206             return i18n("Simulating CD Copy On-The-Fly");
1207         else
1208             return i18n("Simulating CD Copy");
1209     }
1210     else {
1211         if( m_onTheFly )
1212             return i18n("Copying CD On-The-Fly");
1213         else
1214             return i18n("Copying CD");
1215     }
1216 }
1217 
1218 
1219 QString K3b::CdCopyJob::jobDetails() const
1220 {
1221     return i18np("Creating 1 copy",
1222                  "Creating %1 copies",
1223                  (m_simulate||m_onlyCreateImages) ? 1 : m_copies );
1224 }
1225 
1226 
1227 QString K3b::CdCopyJob::jobSource() const
1228 {
1229     if( Device::Device* device = reader() )
1230         return device->vendor() + ' ' + device->description();
1231     else
1232         return QString();
1233 }
1234 
1235 
1236 QString K3b::CdCopyJob::jobTarget() const
1237 {
1238     if( Device::Device* device = writer() )
1239         return device->vendor() + ' ' + device->description();
1240     else
1241         return m_tempPath;
1242 }
1243 
1244 
1245 void K3b::CdCopyJob::finishJob( bool c, bool e )
1246 {
1247     if( d->running ) {
1248         if( c ) {
1249             d->canceled = true;
1250             emit canceled();
1251         }
1252         if( e )
1253             d->error = true;
1254 
1255         cleanup();
1256 
1257         d->running = false;
1258 
1259         jobFinished( !(c||e) );
1260     }
1261 }
1262 
1263 #include "moc_k3bcdcopyjob.cpp"