File indexing completed on 2024-06-16 07:42:05

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 #include "k3bdvdcopyjob.h"
0009 #include "k3blibdvdcss.h"
0010 
0011 #include "k3breadcdreader.h"
0012 #include "k3bdatatrackreader.h"
0013 #include "k3bexternalbinmanager.h"
0014 #include "k3bdevice.h"
0015 #include "k3bdeviceglobals.h"
0016 #include "k3bdevicehandler.h"
0017 #include "k3bdiskinfo.h"
0018 #include "k3bglobals.h"
0019 #include "k3bcore.h"
0020 #include "k3bgrowisofswriter.h"
0021 #include "k3bcdrecordwriter.h"
0022 #include "k3bversion.h"
0023 #include "k3biso9660.h"
0024 #include "k3bfilesplitter.h"
0025 #include "k3bchecksumpipe.h"
0026 #include "k3bverificationjob.h"
0027 #include "k3bglobalsettings.h"
0028 #include "k3b_i18n.h"
0029 
0030 #include <KIO/Global>
0031 
0032 #include <QDebug>
0033 #include <QFile>
0034 #include <QFileInfo>
0035 
0036 
0037 class K3b::DvdCopyJob::Private
0038 {
0039 public:
0040     Private()
0041         : doneCopies(0),
0042           running(false),
0043           canceled(false),
0044           writerJob(0),
0045           readcdReader(0),
0046           dataTrackReader(0),
0047           verificationJob(0),
0048           usedWritingMode(K3b::WritingModeAuto),
0049           verifyData(false) {
0050         outPipe.readFrom( &imageFile, true );
0051     }
0052 
0053     K3b::WritingApp usedWritingApp;
0054 
0055     int doneCopies;
0056 
0057     bool running;
0058     bool readerRunning;
0059     bool writerRunning;
0060     bool canceled;
0061 
0062     K3b::AbstractWriter* writerJob;
0063     K3b::ReadcdReader* readcdReader;
0064     K3b::DataTrackReader* dataTrackReader;
0065     K3b::VerificationJob* verificationJob;
0066 
0067     K3b::Device::DiskInfo sourceDiskInfo;
0068 
0069     K3b::Msf lastSector;
0070 
0071     K3b::WritingMode usedWritingMode;
0072 
0073     K3b::FileSplitter imageFile;
0074     K3b::ChecksumPipe inPipe;
0075     K3b::ActivePipe outPipe;
0076 
0077     bool verifyData;
0078 };
0079 
0080 
0081 K3b::DvdCopyJob::DvdCopyJob( K3b::JobHandler* hdl, QObject* parent )
0082     : K3b::BurnJob( hdl, parent ),
0083       m_writerDevice(0),
0084       m_readerDevice(0),
0085       m_onTheFly(false),
0086       m_removeImageFiles(false),
0087       m_simulate(false),
0088       m_speed(1),
0089       m_copies(1),
0090       m_onlyCreateImage(false),
0091       m_ignoreReadErrors(false),
0092       m_readRetries(128),
0093       m_writingMode( K3b::WritingModeAuto )
0094 {
0095     d = new Private();
0096 }
0097 
0098 
0099 K3b::DvdCopyJob::~DvdCopyJob()
0100 {
0101     delete d;
0102 }
0103 
0104 
0105 void K3b::DvdCopyJob::start()
0106 {
0107     jobStarted();
0108     emit burning(false);
0109 
0110     d->canceled = false;
0111     d->running = true;
0112     d->readerRunning = d->writerRunning = false;
0113 
0114     emit newTask( i18n("Checking Source Medium") );
0115     const K3b::ExternalBin* growisofsBin = k3bcore->externalBinManager()->binObject("growisofs");
0116     if( m_onTheFly &&
0117         growisofsBin && growisofsBin->version() < K3b::Version( 5, 12 ) ) {
0118         m_onTheFly = false;
0119         emit infoMessage( i18n("K3b does not support writing on-the-fly with growisofs %1.",
0120                           growisofsBin->version()), MessageError );
0121         emit infoMessage( i18n("Disabling on-the-fly writing."), MessageInfo );
0122     }
0123 
0124     emit newSubTask( i18n("Waiting for source medium") );
0125 
0126     // wait for a source disk
0127     if( waitForMedium( m_readerDevice,
0128                        K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE,
0129                        K3b::Device::MEDIA_WRITABLE_DVD|K3b::Device::MEDIA_DVD_ROM|K3b::Device::MEDIA_BD_ALL ) == Device::MEDIA_UNKNOWN ) {
0130         emit canceled();
0131         d->running = false;
0132         jobFinished( false );
0133         return;
0134     }
0135 
0136     emit newSubTask( i18n("Checking source medium") );
0137 
0138     connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CommandMediaInfo, m_readerDevice ),
0139              SIGNAL(finished(K3b::Device::DeviceHandler*)),
0140              this,
0141              SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) );
0142 }
0143 
0144 
0145 void K3b::DvdCopyJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh )
0146 {
0147     if( d->canceled ) {
0148         emit canceled();
0149         jobFinished(false);
0150         d->running = false;
0151     }
0152 
0153     d->sourceDiskInfo = dh->diskInfo();
0154 
0155     if( dh->diskInfo().empty() || dh->diskInfo().diskState() == K3b::Device::STATE_NO_MEDIA ) {
0156         emit infoMessage( i18n("No source medium found."), MessageError );
0157         jobFinished(false);
0158         d->running = false;
0159     }
0160     else {
0161         // first let's determine which application to use
0162         d->usedWritingApp = writingApp();
0163         const K3b::ExternalBin* cdrecordBin = k3bcore->externalBinManager()->binObject("cdrecord");
0164         const K3b::ExternalBin* growisofsBin = k3bcore->externalBinManager()->binObject("growisofs");
0165         if ( d->usedWritingApp == K3b::WritingAppAuto ) {
0166             // prefer growisofs to wodim, which doesn't work all that great for DVDs
0167             // (and doesn't support BluRay at all)
0168             if ( cdrecordBin && cdrecordBin->hasFeature( "wodim" ) )
0169                 d->usedWritingApp = K3b::WritingAppGrowisofs;
0170             // otherwise, let's default to cdrecord for the time being
0171             // FIXME: use growisofs for non-dao and non-auto mode
0172             else {
0173                 if ( K3b::Device::isBdMedia( d->sourceDiskInfo.mediaType() ) ) {
0174                     if ( cdrecordBin && cdrecordBin->hasFeature( "blu-ray" ) )
0175                         d->usedWritingApp = K3b::WritingAppCdrecord;
0176                     else
0177                         d->usedWritingApp = K3b::WritingAppGrowisofs;
0178                 } else
0179                     d->usedWritingApp = K3b::WritingAppCdrecord;
0180             }
0181         }
0182 
0183         if( m_readerDevice->copyrightProtectionSystemType() == K3b::Device::COPYRIGHT_PROTECTION_CSS ) { // CSS is the only one we support ATM
0184             emit infoMessage( i18n("Found encrypted DVD."), MessageWarning );
0185             // check for libdvdcss
0186             bool haveLibdvdcss = false;
0187             qDebug() << "(K3b::DvdCopyJob) trying to open libdvdcss.";
0188             if( K3b::LibDvdCss* libcss = K3b::LibDvdCss::create() ) {
0189                 qDebug() << "(K3b::DvdCopyJob) succeeded.";
0190                 qDebug() << "(K3b::DvdCopyJob) dvdcss_open(" << m_readerDevice->blockDeviceName() << ") = "
0191                           << libcss->open(m_readerDevice) << Qt::endl;
0192                 haveLibdvdcss = true;
0193 
0194                 delete libcss;
0195             }
0196             else
0197                 qDebug() << "(K3b::DvdCopyJob) failed.";
0198 
0199             if( !haveLibdvdcss ) {
0200                 emit infoMessage( i18n("Cannot copy encrypted DVDs."), MessageError );
0201                 d->running = false;
0202                 jobFinished( false );
0203                 return;
0204             }
0205         }
0206 
0207 
0208         //
0209         // We cannot rely on the kernel to determine the size of the DVD for some reason
0210         // On the other hand it is not always a good idea to rely on the size from the ISO9660
0211         // header since that may be wrong due to some buggy encoder or some boot code appended
0212         // after creating the image.
0213         // That is why we try our best to determine the size of the DVD. For DVD-ROM this is very
0214         // easy since it has only one track. The same goes for single session DVD-R(W) and DVD+R.
0215         // Multisession DVDs we will simply not copy. ;)
0216         // For DVD+RW and DVD-RW in restricted overwrite mode we are left with no other choice but
0217         // to use the ISO9660 header.
0218         //
0219         // On the other hand: in on-the-fly mode growisofs determines the size of the data to be written
0220         //                    by looking at the ISO9660 header when writing in DAO mode. So in this case
0221         //                    it would be best for us to do the same....
0222         //
0223         // With growisofs 5.15 we have the option to specify the size of the image to be written in DAO mode.
0224         //
0225 
0226         switch( dh->diskInfo().mediaType() ) {
0227         case K3b::Device::MEDIA_DVD_ROM:
0228         case K3b::Device::MEDIA_DVD_PLUS_R_DL:
0229         case K3b::Device::MEDIA_DVD_R_DL:
0230         case K3b::Device::MEDIA_DVD_R_DL_SEQ:
0231         case K3b::Device::MEDIA_DVD_R_DL_JUMP:
0232             if( !m_onlyCreateImage ) {
0233                 if( dh->diskInfo().numLayers() > 1 &&
0234                     dh->diskInfo().size() > MediaSizeDvd4Gb ) {
0235                     if( !(m_writerDevice->type() & (K3b::Device::DEVICE_DVD_R_DL|K3b::Device::DEVICE_DVD_PLUS_R_DL)) ) {
0236                         emit infoMessage( i18n("The writer does not support writing Double Layer DVDs."), MessageError );
0237                         d->running = false;
0238                         jobFinished(false);
0239                         return;
0240                     }
0241                     else if( growisofsBin && !growisofsBin->hasFeature( "dual-layer" ) ) {
0242                         emit infoMessage( i18n("This growisofs version does not support writing Double Layer DVDs."), MessageError );
0243                         d->running = false;
0244                         jobFinished(false);
0245                         return;
0246                     }
0247                 }
0248             }
0249             Q_FALLTHROUGH();
0250         case K3b::Device::MEDIA_DVD_R:
0251         case K3b::Device::MEDIA_DVD_R_SEQ:
0252         case K3b::Device::MEDIA_DVD_RW:
0253         case K3b::Device::MEDIA_DVD_RW_SEQ:
0254         case K3b::Device::MEDIA_DVD_PLUS_R:
0255         case K3b::Device::MEDIA_BD_ROM:
0256         case K3b::Device::MEDIA_BD_R:
0257         case K3b::Device::MEDIA_BD_R_SRM:
0258 
0259             if( dh->diskInfo().numSessions() > 1 ) {
0260                 emit infoMessage( i18n("K3b does not support copying multi-session DVD or Blu-ray disks."), MessageError );
0261                 d->running = false;
0262                 jobFinished(false);
0263                 return;
0264             }
0265 
0266             // growisofs only uses the size from the PVD for reserving
0267             // writable space in DAO mode
0268             // with version >= 5.15 growisofs supports specifying the size of the track
0269             if( m_writingMode != K3b::WritingModeSao || !m_onTheFly || m_onlyCreateImage ||
0270                 ( growisofsBin && growisofsBin->hasFeature( "daosize" ) ) ||
0271                 d->usedWritingApp == K3b::WritingAppCdrecord ) {
0272                 d->lastSector = dh->toc().lastSector();
0273                 break;
0274             }
0275 
0276             // fallthrough
0277 
0278         case K3b::Device::MEDIA_DVD_PLUS_RW:
0279         case K3b::Device::MEDIA_DVD_RW_OVWR:
0280         case K3b::Device::MEDIA_BD_RE:
0281         {
0282             emit infoMessage( i18n("K3b relies on the size saved in the ISO 9660 header."), MessageWarning );
0283             emit infoMessage( i18n("This might result in a corrupt copy if the source was mastered with buggy software."), MessageWarning );
0284 
0285             K3b::Iso9660 isoF( m_readerDevice, 0 );
0286             if( isoF.open() ) {
0287                 d->lastSector = ((long long)isoF.primaryDescriptor().logicalBlockSize*isoF.primaryDescriptor().volumeSpaceSize)/2048LL - 1;
0288             }
0289             else {
0290                 emit infoMessage( i18n("Unable to determine the ISO 9660 filesystem size."), MessageError );
0291                 jobFinished(false);
0292                 d->running = false;
0293                 return;
0294             }
0295         }
0296         break;
0297 
0298         case K3b::Device::MEDIA_DVD_RAM:
0299             emit infoMessage( i18n("K3b does not support copying DVD-RAM."), MessageError );
0300             jobFinished(false);
0301             d->running = false;
0302             return;
0303 
0304         default:
0305             emit infoMessage( i18n("Unsupported media type."), MessageError );
0306             jobFinished(false);
0307             d->running = false;
0308             return;
0309         }
0310 
0311 
0312         if( !m_onTheFly ) {
0313             //
0314             // Check the image path
0315             //
0316             QFileInfo fi( m_imagePath );
0317             if( !fi.isFile() ||
0318                 questionYesNo( i18n("Do you want to overwrite %1?",m_imagePath),
0319                                i18n("File Exists") ) ) {
0320                 if( fi.isDir() )
0321                     m_imagePath = K3b::findTempFile( "iso", m_imagePath );
0322                 else if( !QFileInfo( m_imagePath.section( '/', 0, -2 ) ).isDir() ) {
0323                     emit infoMessage( i18n("Specified an unusable temporary path. Using default."), MessageWarning );
0324                     m_imagePath = K3b::findTempFile( "iso" );
0325                 }
0326                 // else the user specified a file in an existing dir
0327 
0328                 emit infoMessage( i18n("Writing image file to %1.",m_imagePath), MessageInfo );
0329                 emit newSubTask( i18n("Reading source medium.") );
0330             }
0331             else {
0332                 jobFinished(false);
0333                 d->running = false;
0334                 return;
0335             }
0336 
0337             //
0338             // check free temp space
0339             //
0340             KIO::filesize_t imageSpaceNeeded = (KIO::filesize_t)(d->lastSector.lba()+1)*2048;
0341             unsigned long avail, size;
0342             QString pathToTest = m_imagePath.left( m_imagePath.lastIndexOf( '/' ) );
0343             if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) {
0344                 emit infoMessage( i18n("Unable to determine free space in temporary folder '%1'.",pathToTest), MessageError );
0345                 jobFinished(false);
0346                 d->running = false;
0347                 return;
0348             }
0349             else {
0350                 if( avail < imageSpaceNeeded/1024 ) {
0351                     emit infoMessage( i18n("Not enough space left in temporary folder."), MessageError );
0352                     jobFinished(false);
0353                     d->running = false;
0354                     return;
0355                 }
0356             }
0357 
0358             d->imageFile.setName( m_imagePath );
0359             if( !d->imageFile.open( QIODevice::WriteOnly ) ) {
0360                 emit infoMessage( i18n("Unable to open '%1' for writing.",m_imagePath), MessageError );
0361                 jobFinished( false );
0362                 d->running = false;
0363                 return;
0364             }
0365         }
0366 
0367         if( K3b::isMounted( m_readerDevice ) ) {
0368             emit infoMessage( i18n("Unmounting source medium"), MessageInfo );
0369             K3b::unmount( m_readerDevice );
0370         }
0371 
0372         if( m_onlyCreateImage || !m_onTheFly ) {
0373             emit newTask( i18n("Creating image") );
0374         }
0375         else if( m_onTheFly && !m_onlyCreateImage ) {
0376             if( waitForDvd() ) {
0377                 prepareWriter();
0378                 if( m_simulate )
0379                     emit newTask( i18n("Simulating copy") );
0380                 else if( m_copies > 1 )
0381                     emit newTask( i18n("Writing copy %1",d->doneCopies+1) );
0382                 else
0383                     emit newTask( i18n("Writing copy") );
0384 
0385                 emit burning(true);
0386                 d->writerRunning = true;
0387                 d->writerJob->start();
0388             }
0389             else {
0390                 if( d->canceled )
0391                     emit canceled();
0392                 jobFinished(false);
0393                 d->running = false;
0394                 return;
0395             }
0396         }
0397 
0398         prepareReader();
0399         d->readerRunning = true;
0400         d->dataTrackReader->start();
0401     }
0402 }
0403 
0404 
0405 void K3b::DvdCopyJob::cancel()
0406 {
0407     if( d->running ) {
0408         d->canceled = true;
0409         if( d->readerRunning  )
0410             d->dataTrackReader->cancel();
0411         if( d->writerRunning )
0412             d->writerJob->cancel();
0413         if ( d->verificationJob && d->verificationJob->active() )
0414             d->verificationJob->cancel();
0415         d->inPipe.close();
0416         d->outPipe.close();
0417         d->imageFile.close();
0418     }
0419     else {
0420         qDebug() << "(K3b::DvdCopyJob) not running.";
0421     }
0422 }
0423 
0424 
0425 void K3b::DvdCopyJob::prepareReader()
0426 {
0427     if( !d->dataTrackReader ) {
0428         d->dataTrackReader = new K3b::DataTrackReader( this );
0429         connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) );
0430         connect( d->dataTrackReader, SIGNAL(processedSize(int,int)), this, SLOT(slotReaderProcessedSize(int,int)) );
0431         connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) );
0432         connect( d->dataTrackReader, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
0433         connect( d->dataTrackReader, SIGNAL(newTask(QString)), this, SIGNAL(newSubTask(QString)) );
0434         connect( d->dataTrackReader, SIGNAL(debuggingOutput(QString,QString)),
0435                  this, SIGNAL(debuggingOutput(QString,QString)) );
0436     }
0437 
0438     d->dataTrackReader->setDevice( m_readerDevice );
0439     d->dataTrackReader->setIgnoreErrors( m_ignoreReadErrors );
0440     d->dataTrackReader->setRetries( m_readRetries );
0441     d->dataTrackReader->setSectorRange( 0, d->lastSector );
0442 
0443     if( m_onTheFly && !m_onlyCreateImage )
0444         // there are several uses of pipe->writeTo( d->writerJob->ioDevice(), ... ) in this file!
0445 #ifdef __GNUC__
0446 #warning Growisofs needs stdin to be closed in order to exit gracefully. Cdrecord does not. However,  if closed with cdrecord we loose parts of stderr. Why?
0447 #endif
0448         d->inPipe.writeTo( d->writerJob->ioDevice(), d->usedWritingApp == K3b::WritingAppGrowisofs );
0449     else
0450         d->inPipe.writeTo( &d->imageFile, true );
0451 
0452     d->inPipe.open( true );
0453     d->dataTrackReader->writeTo( &d->inPipe );
0454 }
0455 
0456 
0457 // ALWAYS CALL WAITFORDVD BEFORE PREPAREWRITER!
0458 void K3b::DvdCopyJob::prepareWriter()
0459 {
0460     delete d->writerJob;
0461 
0462     if ( d->usedWritingApp == K3b::WritingAppGrowisofs ) {
0463         K3b::GrowisofsWriter* job = new K3b::GrowisofsWriter( m_writerDevice, this, this );
0464 
0465         // these do only make sense with DVD-R(W)
0466         job->setSimulate( m_simulate );
0467         job->setBurnSpeed( m_speed );
0468         job->setWritingMode( d->usedWritingMode );
0469         job->setCloseDvd( true );
0470 
0471         //
0472         // In case the first layer size is not known let the
0473         // split be determined by growisofs
0474         //
0475         if( d->sourceDiskInfo.numLayers() > 1 &&
0476             d->sourceDiskInfo.firstLayerSize() > 0 ) {
0477             job->setLayerBreak( d->sourceDiskInfo.firstLayerSize().lba() );
0478         }
0479         else {
0480             // this is only used in DAO mode with growisofs >= 5.15
0481             job->setTrackSize( d->lastSector.lba()+1 );
0482         }
0483 
0484         job->setImageToWrite( QString() ); // write to stdin
0485 
0486         d->writerJob = job;
0487     }
0488 
0489     else {
0490         K3b::CdrecordWriter* writer = new K3b::CdrecordWriter( m_writerDevice, this, this );
0491 
0492         writer->setWritingMode( d->usedWritingMode );
0493         writer->setSimulate( m_simulate );
0494         writer->setBurnSpeed( m_speed );
0495 
0496         writer->addArgument( "-data" );
0497         writer->addArgument( QString("-tsize=%1s").arg( d->lastSector.lba()+1 ) )->addArgument("-");
0498 
0499         d->writerJob = writer;
0500     }
0501 
0502 
0503     connect( d->writerJob, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
0504     connect( d->writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterProgress(int)) );
0505     connect( d->writerJob, SIGNAL(processedSize(int,int)), this, SIGNAL(processedSize(int,int)) );
0506     connect( d->writerJob, SIGNAL(processedSubSize(int,int)), this, SIGNAL(processedSubSize(int,int)) );
0507     connect( d->writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) );
0508     connect( d->writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) );
0509     connect( d->writerJob, SIGNAL(writeSpeed(int,K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int,K3b::Device::SpeedMultiplicator)) );
0510     connect( d->writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) );
0511     //  connect( d->writerJob, SIGNAL(newTask(QString)), this, SIGNAL(newTask(QString)) );
0512     connect( d->writerJob, SIGNAL(newSubTask(QString)), this, SIGNAL(newSubTask(QString)) );
0513     connect( d->writerJob, SIGNAL(debuggingOutput(QString,QString)),
0514              this, SIGNAL(debuggingOutput(QString,QString)) );
0515 }
0516 
0517 
0518 void K3b::DvdCopyJob::slotReaderProgress( int p )
0519 {
0520     if( !m_onTheFly || m_onlyCreateImage ) {
0521         emit subPercent( p );
0522 
0523         int bigParts = ( m_onlyCreateImage ? 1 : (m_simulate ? 2 : ( d->verifyData ? m_copies*2 : m_copies ) + 1 ) );
0524         emit percent( p/bigParts );
0525     }
0526 }
0527 
0528 
0529 void K3b::DvdCopyJob::slotReaderProcessedSize( int p, int c )
0530 {
0531     if( !m_onTheFly || m_onlyCreateImage )
0532         emit processedSubSize( p, c );
0533 
0534     if( m_onlyCreateImage )
0535         emit processedSize( p, c );
0536 }
0537 
0538 
0539 void K3b::DvdCopyJob::slotWriterProgress( int p )
0540 {
0541     int bigParts = ( m_simulate ? 1 : ( d->verifyData ? m_copies*2 : m_copies ) ) + ( m_onTheFly ? 0 : 1 );
0542     int doneParts = ( m_simulate ? 0 : ( d->verifyData ? d->doneCopies*2 : d->doneCopies ) ) + ( m_onTheFly ? 0 : 1 );
0543     emit percent( 100*doneParts/bigParts + p/bigParts );
0544 
0545     emit subPercent( p );
0546 }
0547 
0548 
0549 void K3b::DvdCopyJob::slotVerificationProgress( int p )
0550 {
0551     int bigParts = ( m_simulate ? 1 : ( d->verifyData ? m_copies*2 : m_copies ) ) + ( m_onTheFly ? 0 : 1 );
0552     int doneParts = ( m_simulate ? 0 : ( d->verifyData ? d->doneCopies*2 : d->doneCopies ) ) + ( m_onTheFly ? 0 : 1 ) + 1;
0553     emit percent( 100*doneParts/bigParts + p/bigParts );
0554 }
0555 
0556 
0557 void K3b::DvdCopyJob::slotReaderFinished( bool success )
0558 {
0559     d->readerRunning = false;
0560 
0561     // already finished?
0562     if( !d->running )
0563         return;
0564 
0565     if( d->canceled ) {
0566         removeImageFiles();
0567         emit canceled();
0568         jobFinished(false);
0569         d->running = false;
0570     }
0571 
0572     if( success ) {
0573         emit infoMessage( i18n("Successfully read source medium."), MessageSuccess );
0574         if( m_onlyCreateImage ) {
0575             jobFinished(true);
0576             d->running = false;
0577         }
0578         else {
0579             if( m_writerDevice == m_readerDevice ) {
0580                 // eject the media (we do this blocking to know if it worked
0581                 // because if it did not it might happen that k3b overwrites a CD-RW
0582                 // source)
0583                 qDebug() << "Ejecting read medium" << m_readerDevice->blockDeviceName();
0584                 if( !K3b::eject( m_readerDevice ) ) {
0585                     blockingInformation( i18n("K3b was unable to eject the source medium. Please do so manually.") );
0586                 }
0587             }
0588 
0589             if( !m_onTheFly ) {
0590 
0591                 d->imageFile.close();
0592 
0593                 if( waitForDvd() ) {
0594                     prepareWriter();
0595                     if( m_copies > 1 )
0596                         emit newTask( i18n("Writing copy %1",d->doneCopies+1) );
0597                     else
0598                         emit newTask( i18n("Writing copy") );
0599 
0600                     emit burning(true);
0601 
0602                     d->writerRunning = true;
0603                     d->writerJob->start();
0604 #ifdef __GNUC__
0605 #warning Growisofs needs stdin to be closed in order to exit gracefully. Cdrecord does not. However,  if closed with cdrecord we loose parts of stderr. Why?
0606 #endif
0607                     d->outPipe.writeTo( d->writerJob->ioDevice(), d->usedWritingApp == K3b::WritingAppGrowisofs );
0608                     d->outPipe.open( true );
0609                 }
0610                 else {
0611                     if( m_removeImageFiles )
0612                         removeImageFiles();
0613                     if( d->canceled )
0614                         emit canceled();
0615                     jobFinished(false);
0616                     d->running = false;
0617                 }
0618             }
0619         }
0620     }
0621     else {
0622         removeImageFiles();
0623         jobFinished(false);
0624         d->running = false;
0625     }
0626 }
0627 
0628 
0629 void K3b::DvdCopyJob::slotWriterFinished( bool success )
0630 {
0631     d->writerRunning = false;
0632 
0633     // already finished?
0634     if( !d->running )
0635         return;
0636 
0637     if( d->canceled ) {
0638         if( m_removeImageFiles )
0639             removeImageFiles();
0640         emit canceled();
0641         jobFinished(false);
0642         d->running = false;
0643     }
0644 
0645     if( success ) {
0646         emit infoMessage( i18n("Successfully written copy %1.",d->doneCopies+1), MessageInfo );
0647 
0648         if( d->verifyData && !m_simulate ) {
0649             if( !d->verificationJob ) {
0650                 d->verificationJob = new K3b::VerificationJob( this, this );
0651                 connect( d->verificationJob, SIGNAL(infoMessage(QString,int)),
0652                          this, SIGNAL(infoMessage(QString,int)) );
0653                 connect( d->verificationJob, SIGNAL(newTask(QString)),
0654                          this, SIGNAL(newSubTask(QString)) );
0655                 connect( d->verificationJob, SIGNAL(percent(int)),
0656                          this, SLOT(slotVerificationProgress(int)) );
0657                 connect( d->verificationJob, SIGNAL(percent(int)),
0658                          this, SIGNAL(subPercent(int)) );
0659                 connect( d->verificationJob, SIGNAL(finished(bool)),
0660                          this, SLOT(slotVerificationFinished(bool)) );
0661                 connect( d->verificationJob, SIGNAL(debuggingOutput(QString,QString)),
0662                          this, SIGNAL(debuggingOutput(QString,QString)) );
0663 
0664             }
0665             d->verificationJob->setDevice( m_writerDevice );
0666             d->verificationJob->addTrack( 1, d->inPipe.checksum(), d->lastSector+1 );
0667 
0668             if( m_copies > 1 )
0669                 emit newTask( i18n("Verifying copy %1",d->doneCopies+1) );
0670             else
0671                 emit newTask( i18n("Verifying copy") );
0672 
0673             emit burning( false );
0674 
0675             d->verificationJob->start();
0676         }
0677 
0678         else if( ++d->doneCopies < m_copies ) {
0679 
0680             if( !K3b::eject( m_writerDevice ) ) {
0681                 blockingInformation( i18n("K3b was unable to eject the written medium. Please do so manually.") );
0682             }
0683 
0684             if( waitForDvd() ) {
0685                 prepareWriter();
0686                 emit newTask( i18n("Writing copy %1",d->doneCopies+1) );
0687 
0688                 emit burning(true);
0689 
0690                 d->writerRunning = true;
0691                 d->writerJob->start();
0692             }
0693             else {
0694                 if( d->canceled )
0695                     emit canceled();
0696                 jobFinished(false);
0697                 d->running = false;
0698                 return;
0699             }
0700 
0701             if( m_onTheFly ) {
0702                 prepareReader();
0703                 d->readerRunning = true;
0704                 d->dataTrackReader->start();
0705             }
0706             else {
0707 #ifdef __GNUC__
0708 #warning Growisofs needs stdin to be closed in order to exit gracefully. Cdrecord does not. However,  if closed with cdrecord we loose parts of stderr. Why?
0709 #endif
0710                 d->outPipe.writeTo( d->writerJob->ioDevice(), true/*d->usedWritingApp == K3b::WritingAppGrowisofs*/ );
0711                 d->outPipe.open( true );
0712             }
0713         }
0714         else {
0715             if ( k3bcore->globalSettings()->ejectMedia() ) {
0716                 K3b::Device::eject( m_writerDevice );
0717             }
0718             if( m_removeImageFiles )
0719                 removeImageFiles();
0720             d->running = false;
0721             jobFinished(true);
0722         }
0723     }
0724     else {
0725         if( m_removeImageFiles )
0726             removeImageFiles();
0727         d->running = false;
0728         jobFinished(false);
0729     }
0730 }
0731 
0732 
0733 void K3b::DvdCopyJob::slotVerificationFinished( bool success )
0734 {
0735     if ( d->canceled ) {
0736         emit canceled();
0737         jobFinished( false );
0738     }
0739 
0740     // we simply ignore the results from the verification, the verification
0741     // job already emits a message
0742     else if( ++d->doneCopies < m_copies ) {
0743 
0744         if( waitForDvd() ) {
0745             prepareWriter();
0746             emit newTask( i18n("Writing copy %1",d->doneCopies+1) );
0747 
0748             emit burning(true);
0749 
0750             d->writerRunning = true;
0751             d->writerJob->start();
0752         }
0753         else {
0754             if( d->canceled )
0755                 emit canceled();
0756             jobFinished(false);
0757             d->running = false;
0758             return;
0759         }
0760 
0761         if( m_onTheFly ) {
0762             prepareReader();
0763             d->readerRunning = true;
0764             d->dataTrackReader->start();
0765         }
0766         else {
0767 #ifdef __GNUC__
0768 #warning Growisofs needs stdin to be closed in order to exit gracefully. Cdrecord does not. However,  if closed with cdrecord we loose parts of stderr. Why?
0769 #endif
0770             d->outPipe.writeTo( d->writerJob->ioDevice(), d->usedWritingApp == K3b::WritingAppGrowisofs );
0771             d->outPipe.open( true );
0772         }
0773     }
0774     else {
0775         if( m_removeImageFiles )
0776             removeImageFiles();
0777         d->running = false;
0778         jobFinished( success );
0779     }
0780 }
0781 
0782 
0783 // this is basically the same code as in K3b::DvdJob... :(
0784 // perhaps this should be moved to some K3b::GrowisofsHandler which also parses the growisofs output?
0785 bool K3b::DvdCopyJob::waitForDvd()
0786 {
0787     if ( !K3b::Device::isDvdMedia( d->sourceDiskInfo.mediaType() ) &&
0788          !K3b::Device::isBdMedia( d->sourceDiskInfo.mediaType() ) ) {
0789         // this should NEVER happen
0790         emit infoMessage( i18n( "Unsupported media type: %1" , K3b::Device::mediaTypeString( d->sourceDiskInfo.mediaType() ) ), MessageError );
0791         return false;
0792     }
0793 
0794     Device::MediaType m = waitForMedium( m_writerDevice,
0795                                          K3b::Device::STATE_EMPTY,
0796                                          Device::MEDIA_WRITABLE_DVD|Device::MEDIA_WRITABLE_BD,
0797                                          d->sourceDiskInfo.size() );
0798 
0799     if( m == Device::MEDIA_UNKNOWN ) {
0800         cancel();
0801         return false;
0802     }
0803 
0804     else {
0805         // -------------------------------
0806         // DVD Plus
0807         // -------------------------------
0808         if( m & K3b::Device::MEDIA_DVD_PLUS_ALL ) {
0809 
0810             if ( m & ( Device::MEDIA_DVD_PLUS_R|Device::MEDIA_DVD_PLUS_R_DL ) )
0811                 d->usedWritingMode = K3b::WritingModeSao;
0812             else
0813                 d->usedWritingMode = K3b::WritingModeRestrictedOverwrite;
0814 
0815             if( m_simulate ) {
0816                 if( !questionYesNo( i18n("%1 media do not support write simulation. "
0817                                          "Do you really want to continue? The disc will actually be "
0818                                          "written to.", Device::mediaTypeString(m, true)),
0819                                     i18n("No Simulation with %1", Device::mediaTypeString(m, true)) ) ) {
0820                     cancel();
0821                     return false;
0822                 }
0823 
0824 //  m_simulate = false;
0825                 emit newTask( i18n("Writing DVD copy") );
0826             }
0827 
0828             if( m_writingMode != K3b::WritingModeAuto && m_writingMode != K3b::WritingModeRestrictedOverwrite )
0829                 emit infoMessage( i18n("Writing mode ignored when writing DVD+R(W) media."), MessageInfo );
0830 
0831             emit infoMessage( i18n("Writing %1.", Device::mediaTypeString( m, true ) ), MessageInfo );
0832         }
0833 
0834         // -------------------------------
0835         // DVD Minus
0836         // -------------------------------
0837         else if ( m & K3b::Device::MEDIA_DVD_MINUS_ALL ) {
0838             if( m_simulate && !m_writerDevice->dvdMinusTestwrite() ) {
0839                 if( !questionYesNo( i18n("Your writer (%1 %2) does not support simulation with DVD-R(W) media. "
0840                                          "Do you really want to continue? The media will actually be "
0841                                          "written to.",
0842                                          m_writerDevice->vendor(),
0843                                          m_writerDevice->description()),
0844                                     i18n("No Simulation with DVD-R(W)") ) ) {
0845                     cancel();
0846                     return false;
0847                 }
0848 
0849 //  m_simulate = false;
0850             }
0851 
0852             //
0853             // We do not default to DAO in onthefly mode since otherwise growisofs would
0854             // use the size from the PVD to reserve space on the DVD and that can be bad
0855             // if this size is wrong
0856             // With growisofs 5.15 we have the option to specify the size of the image to be written in DAO mode.
0857             //
0858 //       bool sizeWithDao = ( k3bcore->externalBinManager()->binObject( "growisofs" ) &&
0859 //             k3bcore->externalBinManager()->binObject( "growisofs" )->version >= K3b::Version( 5, 15, -1 ) );
0860 
0861 
0862             // TODO: check for feature 0x21
0863 
0864             if( m & K3b::Device::MEDIA_DVD_RW_OVWR ) {
0865                 emit infoMessage( i18n("Writing DVD-RW in restricted overwrite mode."), MessageInfo );
0866                 d->usedWritingMode = K3b::WritingModeRestrictedOverwrite;
0867             }
0868             else if( m & (K3b::Device::MEDIA_DVD_RW_SEQ|
0869                           K3b::Device::MEDIA_DVD_RW) ) {
0870                 if( m_writingMode == K3b::WritingModeSao ) {
0871 //      ( m_writingMode ==  K3b::WritingModeAuto &&
0872 //        ( sizeWithDao || !m_onTheFly ) ) ) {
0873                     emit infoMessage( i18n("Writing DVD-RW in DAO mode."), MessageInfo );
0874                     d->usedWritingMode = K3b::WritingModeSao;
0875                 }
0876                 else {
0877                     emit infoMessage( i18n("Writing DVD-RW in incremental mode."), MessageInfo );
0878                     d->usedWritingMode = K3b::WritingModeIncrementalSequential;
0879                 }
0880             }
0881             else {
0882 
0883                 // FIXME: DVD-R DL jump and stuff
0884 
0885                 if( m_writingMode == K3b::WritingModeRestrictedOverwrite )
0886                     emit infoMessage( i18n("Restricted Overwrite is not possible with DVD-R media."), MessageInfo );
0887 
0888                 if( m_writingMode == K3b::WritingModeSao ) {
0889 //      ( m_writingMode ==  K3b::WritingModeAuto &&
0890 //        ( sizeWithDao || !m_onTheFly ) ) ) {
0891                     emit infoMessage( i18n("Writing %1 in DAO mode.",K3b::Device::mediaTypeString(m, true) ), MessageInfo );
0892                     d->usedWritingMode = K3b::WritingModeSao;
0893                 }
0894                 else {
0895                     emit infoMessage( i18n("Writing %1 in incremental mode.",K3b::Device::mediaTypeString(m, true) ), MessageInfo );
0896                     d->usedWritingMode = K3b::WritingModeIncrementalSequential;
0897                 }
0898             }
0899         }
0900 
0901 
0902         // -------------------------------
0903         // Blu-ray
0904         // -------------------------------
0905         else {
0906             d->usedWritingMode = K3b::WritingModeSao;
0907 
0908             if( m_simulate ) {
0909                 if( !questionYesNo( i18n("%1 media do not support write simulation. "
0910                                          "Do you really want to continue? The disc will actually be "
0911                                          "written to.", Device::mediaTypeString(m, true)),
0912                                     i18n("No Simulation with %1", Device::mediaTypeString(m, true)) ) ) {
0913                     cancel();
0914                     return false;
0915                 }
0916 
0917                 m_simulate = false;
0918                 emit newTask( i18n("Writing BD copy") );
0919             }
0920 
0921             emit infoMessage( i18n("Writing %1.", Device::mediaTypeString(m, true) ), MessageInfo );
0922         }
0923     }
0924 
0925     return true;
0926 }
0927 
0928 
0929 
0930 void K3b::DvdCopyJob::removeImageFiles()
0931 {
0932     if( QFile::exists( m_imagePath ) ) {
0933         d->imageFile.remove();
0934         emit infoMessage( i18n("Removed image file %1",m_imagePath), K3b::Job::MessageSuccess );
0935     }
0936 }
0937 
0938 
0939 QString K3b::DvdCopyJob::jobDescription() const
0940 {
0941     if( m_onlyCreateImage ) {
0942         return i18n("Creating Image");
0943     }
0944     else {
0945         if( m_onTheFly )
0946             return i18n("Copying DVD or BD On-The-Fly");
0947         else
0948             return i18n("Copying DVD or BD");
0949     }
0950 }
0951 
0952 
0953 QString K3b::DvdCopyJob::jobDetails() const
0954 {
0955     return i18np("Creating 1 copy",
0956                  "Creating %1 copies",
0957                  (m_simulate||m_onlyCreateImage) ? 1 : m_copies );
0958 }
0959 
0960 
0961 QString K3b::DvdCopyJob::jobSource() const
0962 {
0963     if( Device::Device* device = readingDevice() )
0964         return device->vendor() + ' ' + device->description();
0965     else
0966         return QString();
0967 }
0968 
0969 
0970 QString K3b::DvdCopyJob::jobTarget() const
0971 {
0972     if( Device::Device* device = writer() )
0973         return device->vendor() + ' ' + device->description();
0974     else
0975         return m_imagePath;
0976 }
0977 
0978 
0979 void K3b::DvdCopyJob::setVerifyData( bool b )
0980 {
0981     d->verifyData = b;
0982 }
0983 
0984 #include "moc_k3bdvdcopyjob.cpp"