/* * * $Id.cpp,v 1.82 2005/02/04 09:27:19 trueg Exp $ * Copyright (C) 2003-2007 Sebastian Trueg * * This file is part of the K3b project. * Copyright (C) 1998-2007 Sebastian Trueg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include "k3bcdcopyjob.h" #include "k3baudiosessionreadingjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K3bCdCopyJob::Private { public: Private() : canceled(false), running(false), readcdReader(0), dataTrackReader(0), audioSessionReader(0), cdrecordWriter(0), infFileWriter(0), cddb(0) { } bool canceled; bool error; bool readingSuccessful; bool running; unsigned int numSessions; bool doNotCloseLastSession; unsigned int doneCopies; unsigned int currentReadSession; unsigned int currentWrittenSession; K3bDevice::Toc toc; TQByteArray cdTextRaw; K3bReadcdReader* readcdReader; K3bDataTrackReader* dataTrackReader; K3bAudioSessionReadingJob* audioSessionReader; K3bCdrecordWriter* cdrecordWriter; K3bInfFileWriter* infFileWriter; bool audioReaderRunning; bool dataReaderRunning; bool writerRunning; // image filenames, one for every track TQStringList imageNames; // inf-filenames for writing audio tracks TQStringList infNames; // indicates if we created a dir or not bool deleteTempDir; K3bCddb* cddb; K3bCddbResultEntry cddbInfo; bool haveCddb; bool haveCdText; TQValueVector dataSessionProbablyTAORecorded; // used to determine progress TQValueVector sessionSizes; long overallSize; }; K3bCdCopyJob::K3bCdCopyJob( K3bJobHandler* hdl, TQObject* parent ) : K3bBurnJob( hdl, parent ), m_simulate(false), m_copies(1), m_onlyCreateImages(false), m_onTheFly(true), m_ignoreDataReadErrors(false), m_ignoreAudioReadErrors(true), m_noCorrection(false), m_dataReadRetries(128), m_audioReadRetries(5), m_preferCdText(false), m_copyCdText(true), m_writingMode( K3b::WRITING_MODE_AUTO ) { d = new Private(); } K3bCdCopyJob::~K3bCdCopyJob() { delete d->infFileWriter; delete d; } void K3bCdCopyJob::start() { d->running = true; d->canceled = false; d->error = false; d->readingSuccessful = false; d->audioReaderRunning = d->dataReaderRunning = d->writerRunning = false; d->sessionSizes.clear(); d->dataSessionProbablyTAORecorded.clear(); d->deleteTempDir = false; d->haveCdText = false; d->haveCddb = false; jobStarted(); emit newTask( i18n("Checking Source Medium") ); emit burning(false); emit newSubTask( i18n("Waiting for source medium") ); // wait for a source disk if( waitForMedia( m_readerDevice, K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE, K3bDevice::MEDIA_WRITABLE_CD|K3bDevice::MEDIA_CD_ROM ) < 0 ) { finishJob( true, false ); return; } emit newSubTask( i18n("Checking source medium") ); // FIXME: read ISRCs and MCN connect( K3bDevice::diskInfo( m_readerDevice ), TQT_SIGNAL(finished(K3bDevice::DeviceHandler*)), this, TQT_SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) ); } void K3bCdCopyJob::slotDiskInfoReady( K3bDevice::DeviceHandler* dh ) { if( dh->success() ) { d->toc = dh->toc(); // // for now we copy audio, pure data (aka 1 data track), cd-extra (2 session, audio and data), // and data multisession which one track per session. // Everything else will be rejected // bool canCopy = true; bool audio = false; d->numSessions = dh->diskInfo().numSessions(); d->doNotCloseLastSession = (dh->diskInfo().diskState() == K3bDevice::STATE_INCOMPLETE); switch( dh->toc().contentType() ) { case K3bDevice::DATA: // check if every track is in it's own session // only then we copy the cd if( (int)dh->toc().count() != dh->diskInfo().numSessions() ) { emit infoMessage( i18n("K3b does not copy CDs containing multiple data tracks."), ERROR ); canCopy = false; } else if( dh->diskInfo().numSessions() > 1 ) emit infoMessage( i18n("Copying Multisession Data CD."), INFO ); else emit infoMessage( i18n("Copying Data CD."), INFO ); break; case K3bDevice::MIXED: audio = true; if( dh->diskInfo().numSessions() != 2 || d->toc[0].type() != K3bDevice::Track::AUDIO ) { emit infoMessage( i18n("K3b can only copy CD-Extra mixed mode CDs."), ERROR ); canCopy = false; } else emit infoMessage( i18n("Copying Enhanced Audio CD (CD-Extra)."), INFO ); break; case K3bDevice::AUDIO: audio = true; emit infoMessage( i18n("Copying Audio CD."), INFO ); break; case K3bDevice::NONE: default: emit infoMessage( i18n("The source disk is empty."), ERROR ); canCopy = false; break; } // // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain // zero data anyway. The problem is that I do not know of a valid method to determine if a track // was written in TAO (the control nibble does definitely not work, I never saw one which did not // equal 4). // So the solution for now is to simply try to read the last sector of a data track. If this is not // possible we assume it was written in TAO mode and reduce the length by 2 sectors // unsigned char buffer[2048]; int i = 1; for( K3bDevice::Toc::iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { if( (*it).type() == K3bDevice::Track::DATA ) { // we try twice just to be sure if( m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) || m_readerDevice->read10( buffer, 2048, (*it).lastSector().lba(), 1 ) ) { d->dataSessionProbablyTAORecorded.append(false); kdDebug() << "(K3bCdCopyJob) track " << i << " probably DAO recorded." << endl; } else { d->dataSessionProbablyTAORecorded.append(true); kdDebug() << "(K3bCdCopyJob) track " << i << " probably TAO recorded." << endl; } } ++i; } // // To copy mode2 data tracks we need cdrecord >= 2.01a12 which introduced the -xa1 and -xamix options // if( k3bcore->externalBinManager()->binObject("cdrecord") && !k3bcore->externalBinManager()->binObject("cdrecord")->hasFeature( "xamix" ) ) { for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { if( (*it).type() == K3bDevice::Track::DATA && ( (*it).mode() == K3bDevice::Track::XA_FORM1 || (*it).mode() == K3bDevice::Track::XA_FORM2 ) ) { emit infoMessage( i18n("K3b needs cdrecord 2.01a12 or newer to copy Mode2 data tracks."), ERROR ); finishJob( true, false ); return; } } } // // It is not possible to create multisession cds in raw writing mode // if( d->numSessions > 1 && m_writingMode == K3b::RAW ) { if( !questionYesNo( i18n("You will only be able to copy the first session in raw writing mode. " "Continue anyway?"), i18n("Multisession CD") ) ) { finishJob( true, false ); return; } else { emit infoMessage( i18n("Only copying first session."), WARNING ); // TODO: remove the second session from the progress stuff } } // // We already create the temp filenames here since we need them to check the free space // if( !m_onTheFly || m_onlyCreateImages ) { if( !prepareImageFiles() ) { finishJob( false, true ); return; } // // check free temp space // TDEIO::filesize_t imageSpaceNeeded = 0; for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { if( (*it).type() == K3bDevice::Track::AUDIO ) imageSpaceNeeded += (*it).length().audioBytes() + 44; else imageSpaceNeeded += (*it).length().mode1Bytes(); } unsigned long avail, size; TQString pathToTest = m_tempPath.left( m_tempPath.findRev( '/' ) ); if( !K3b::kbFreeOnFs( pathToTest, size, avail ) ) { emit infoMessage( i18n("Unable to determine free space in temporary directory '%1'.").arg(pathToTest), ERROR ); d->error = true; canCopy = false; } else { if( avail < imageSpaceNeeded/1024 ) { emit infoMessage( i18n("Not enough space left in temporary directory."), ERROR ); d->error = true; canCopy = false; } } } if( canCopy ) { if( K3b::isMounted( m_readerDevice ) ) { emit infoMessage( i18n("Unmounting source medium"), INFO ); K3b::unmount( m_readerDevice ); } d->overallSize = 0; // now create some progress helper values for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { d->overallSize += (*it).length().lba(); if( d->sessionSizes.isEmpty() || (*it).type() == K3bDevice::Track::DATA ) d->sessionSizes.append( (*it).length().lba() ); else d->sessionSizes[0] += (*it).length().lba(); } if( audio && !m_onlyCreateImages ) { if( m_copyCdText ) searchCdText(); else queryCddb(); } else startCopy(); } else { finishJob( false, true ); } } else { emit infoMessage( i18n("Unable to read TOC"), ERROR ); finishJob( false, true ); } } void K3bCdCopyJob::searchCdText() { emit newSubTask( i18n("Searching CD-TEXT") ); connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::CD_TEXT_RAW, m_readerDevice ), TQT_SIGNAL(finished(K3bDevice::DeviceHandler*)), this, TQT_SLOT(slotCdTextReady(K3bDevice::DeviceHandler*)) ); } void K3bCdCopyJob::slotCdTextReady( K3bDevice::DeviceHandler* dh ) { if( dh->success() ) { if( K3bDevice::CdText::checkCrc( dh->cdTextRaw() ) ) { K3bDevice::CdText cdt( dh->cdTextRaw() ); emit infoMessage( i18n("Found CD-TEXT (%1 - %2).").arg(cdt.performer()).arg(cdt.title()), SUCCESS ); d->haveCdText = true; d->cdTextRaw = dh->cdTextRaw(); } else { emit infoMessage( i18n("Found corrupted CD-TEXT. Ignoring it."), WARNING ); d->haveCdText = false; } if( d->haveCdText && m_preferCdText ) startCopy(); else queryCddb(); } else { emit infoMessage( i18n("No CD-TEXT found."), INFO ); d->haveCdText = false; queryCddb(); } } void K3bCdCopyJob::queryCddb() { emit newSubTask( i18n("Querying Cddb") ); d->haveCddb = false; if( !d->cddb ) { d->cddb = new K3bCddb( this ); connect( d->cddb, TQT_SIGNAL(queryFinished(int)), this, TQT_SLOT(slotCddbQueryFinished(int)) ); } TDEConfig* c = k3bcore->config(); c->setGroup("Cddb"); d->cddb->readConfig( c ); d->cddb->query( d->toc ); } void K3bCdCopyJob::slotCddbQueryFinished( int error ) { if( error == K3bCddbQuery::SUCCESS ) { d->cddbInfo = d->cddb->result(); d->haveCddb = true; emit infoMessage( i18n("Found Cddb entry (%1 - %2).").arg(d->cddbInfo.cdArtist).arg(d->cddbInfo.cdTitle), SUCCESS ); // save the entry locally TDEConfig* c = k3bcore->config(); c->setGroup( "Cddb" ); if( c->readBoolEntry( "save cddb entries locally", true ) ) d->cddb->saveEntry( d->cddbInfo ); } else if( error == K3bCddbQuery::NO_ENTRY_FOUND ) { emit infoMessage( i18n("No Cddb entry found."), WARNING ); } else { emit infoMessage( i18n("Cddb error (%1).").arg(d->cddb->errorString()), ERROR ); } startCopy(); } void K3bCdCopyJob::startCopy() { d->currentWrittenSession = d->currentReadSession = 1; d->doneCopies = 0; if( m_onTheFly ) { emit newSubTask( i18n("Preparing write process...") ); if( writeNextSession() ) readNextSession(); else { finishJob( d->canceled, d->error ); } } else readNextSession(); } void K3bCdCopyJob::cancel() { d->canceled = true; if( d->writerRunning ) { // // we will handle cleanup in slotWriterFinished() // if we are writing onthefly the reader won't be able to write // anymore and will finish unsuccessfully, too // d->cdrecordWriter->cancel(); } else if( d->audioReaderRunning ) d->audioSessionReader->cancel(); else if( d->dataReaderRunning ) // d->readcdReader->cancel(); d->dataTrackReader->cancel(); } bool K3bCdCopyJob::prepareImageFiles() { kdDebug() << "(K3bCdCopyJob) prepareImageFiles()" << endl; d->imageNames.clear(); d->infNames.clear(); d->deleteTempDir = false; TQFileInfo fi( m_tempPath ); if( d->toc.count() > 1 || d->toc.contentType() == K3bDevice::AUDIO ) { // create a directory which contains all the images and inf and stuff // and save it in some cool structure bool tempDirReady = false; if( !fi.isDir() ) { if( TQFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) { if( !TQFile::exists( m_tempPath ) ) { TQDir dir( m_tempPath.section( '/', 0, -2 ) ); dir.mkdir( m_tempPath.section( '/', -1 ) ); tempDirReady = true; } else m_tempPath = m_tempPath.section( '/', 0, -2 ); } else { emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); m_tempPath = K3b::defaultTempPath(); } } // create temp dir if( !tempDirReady ) { TQDir dir( m_tempPath ); m_tempPath = K3b::findUniqueFilePrefix( "k3bCdCopy", m_tempPath ); kdDebug() << "(K3bCdCopyJob) creating temp dir: " << m_tempPath << endl; if( !dir.mkdir( m_tempPath, true ) ) { emit infoMessage( i18n("Unable to create temporary directory '%1'.").arg(m_tempPath), ERROR ); return false; } d->deleteTempDir = true; } m_tempPath = K3b::prepareDir( m_tempPath ); emit infoMessage( i18n("Using temporary directory %1.").arg(m_tempPath), INFO ); // create temp filenames int i = 1; for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { if( (*it).type() == K3bDevice::Track::AUDIO ) { d->imageNames.append( m_tempPath + TQString("Track%1.wav").arg(TQString::number(i).rightJustify(2, '0')) ); d->infNames.append( m_tempPath + TQString("Track%1.inf").arg(TQString::number(i).rightJustify(2, '0')) ); } else d->imageNames.append( m_tempPath + TQString("Track%1.iso").arg(TQString::number(i).rightJustify(2, '0')) ); ++i; } kdDebug() << "(K3bCdCopyJob) created image filenames:" << endl; for( unsigned int i = 0; i < d->imageNames.count(); ++i ) kdDebug() << "(K3bCdCopyJob) " << d->imageNames[i] << endl; return true; } else { // we only need a single image file if( !fi.isFile() || questionYesNo( i18n("Do you want to overwrite %1?").arg(m_tempPath), i18n("File Exists") ) ) { if( fi.isDir() ) m_tempPath = K3b::findTempFile( "iso", m_tempPath ); else if( !TQFileInfo( m_tempPath.section( '/', 0, -2 ) ).isDir() ) { emit infoMessage( i18n("Specified an unusable temporary path. Using default."), WARNING ); m_tempPath = K3b::findTempFile( "iso" ); } // else the user specified a file in an existing dir emit infoMessage( i18n("Writing image file to %1.").arg(m_tempPath), INFO ); } else return false; d->imageNames.append( m_tempPath ); return true; } } void K3bCdCopyJob::readNextSession() { if( !m_onTheFly || m_onlyCreateImages ) { if( d->numSessions > 1 ) emit newTask( i18n("Reading Session %1").arg(d->currentReadSession) ); else emit newTask( i18n("Reading Source Medium") ); if( d->currentReadSession == 1 ) emit newSubTask( i18n("Reading track %1 of %2").arg(1).arg(d->toc.count()) ); } // there is only one situation where we need the audiosessionreader: // if the first session is an audio session. That means the first track // is an audio track if( d->currentReadSession == 1 && d->toc[0].type() == K3bDevice::Track::AUDIO ) { if( !d->audioSessionReader ) { d->audioSessionReader = new K3bAudioSessionReadingJob( this, this ); connect( d->audioSessionReader, TQT_SIGNAL(nextTrack(int, int)), this, TQT_SLOT(slotReadingNextTrack(int, int)) ); connectSubJob( d->audioSessionReader, TQT_SLOT(slotSessionReaderFinished(bool)), true, TQT_SLOT(slotReaderProgress(int)), TQT_SLOT(slotReaderSubProgress(int)) ); } d->audioSessionReader->setDevice( m_readerDevice ); d->audioSessionReader->setToc( d->toc ); d->audioSessionReader->setParanoiaMode( m_paranoiaMode ); d->audioSessionReader->setReadRetries( m_audioReadRetries ); d->audioSessionReader->setNeverSkip( !m_ignoreAudioReadErrors ); if( m_onTheFly ) d->audioSessionReader->writeToFd( d->cdrecordWriter->fd() ); else d->audioSessionReader->setImageNames( d->imageNames ); // the audio tracks are always the first tracks d->audioReaderRunning = true; d->audioSessionReader->start(); } else { if( !d->dataTrackReader ) { d->dataTrackReader = new K3bDataTrackReader( this, this ); connect( d->dataTrackReader, TQT_SIGNAL(percent(int)), this, TQT_SLOT(slotReaderProgress(int)) ); connect( d->dataTrackReader, TQT_SIGNAL(processedSize(int, int)), this, TQT_SLOT(slotReaderProcessedSize(int, int)) ); connect( d->dataTrackReader, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotSessionReaderFinished(bool)) ); connect( d->dataTrackReader, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) ); connect( d->dataTrackReader, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)), this, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)) ); } d->dataTrackReader->setDevice( m_readerDevice ); d->dataTrackReader->setIgnoreErrors( m_ignoreDataReadErrors ); d->dataTrackReader->setNoCorrection( m_noCorrection ); d->dataTrackReader->setRetries( m_dataReadRetries ); if( m_onlyCreateImages ) d->dataTrackReader->setSectorSize( K3bDataTrackReader::MODE1 ); else d->dataTrackReader->setSectorSize( K3bDataTrackReader::AUTO ); K3bTrack* track = 0; unsigned int dataTrackIndex = 0; if( d->toc.contentType() == K3bDevice::MIXED ) { track = &d->toc[d->toc.count()-1]; dataTrackIndex = 0; } else { track = &d->toc[d->currentReadSession-1]; // only one track per session dataTrackIndex = d->currentReadSession-1; } // HACK: if the track is TAO recorded cut the two run-out sectors if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex && d->dataSessionProbablyTAORecorded[dataTrackIndex] ) d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() - 2 ); else d->dataTrackReader->setSectorRange( track->firstSector(), track->lastSector() ); int trackNum = d->currentReadSession; if( d->toc.contentType() == K3bDevice::MIXED ) trackNum = d->toc.count(); if( m_onTheFly ) d->dataTrackReader->writeToFd( d->cdrecordWriter->fd() ); else d->dataTrackReader->setImagePath( d->imageNames[trackNum-1] ); d->dataReaderRunning = true; if( !m_onTheFly || m_onlyCreateImages ) slotReadingNextTrack( 1, 1 ); d->dataTrackReader->start(); } } bool K3bCdCopyJob::writeNextSession() { // we emit our own task since the cdrecord task is way too simple if( d->numSessions > 1 ) { if( m_simulate ) emit newTask( i18n("Simulating Session %1").arg(d->currentWrittenSession) ); else if( m_copies > 1 ) emit newTask( i18n("Writing Copy %1 (Session %2)").arg(d->doneCopies+1).arg(d->currentWrittenSession) ); else emit newTask( i18n("Writing Copy (Session %2)").arg(d->currentWrittenSession) ); } else { if( m_simulate ) emit newTask( i18n("Simulating") ); else if( m_copies > 1 ) emit newTask( i18n("Writing Copy %1").arg(d->doneCopies+1) ); else emit newTask( i18n("Writing Copy") ); } emit newSubTask( i18n("Waiting for media") ); // if session > 1 we wait for an appendable CD if( waitForMedia( m_writerDevice, d->currentWrittenSession > 1 && !m_simulate ? K3bDevice::STATE_INCOMPLETE : K3bDevice::STATE_EMPTY, K3bDevice::MEDIA_WRITABLE_CD ) < 0 ) { finishJob( true, false ); return false; } if( !d->cdrecordWriter ) { d->cdrecordWriter = new K3bCdrecordWriter( m_writerDevice, this, this ); connect( d->cdrecordWriter, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(percent(int)), this, TQT_SLOT(slotWriterProgress(int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(processedSize(int, int)), this, TQT_SIGNAL(processedSize(int, int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(subPercent(int)), this, TQT_SIGNAL(subPercent(int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(processedSubSize(int, int)), this, TQT_SIGNAL(processedSubSize(int, int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(nextTrack(int, int)), this, TQT_SLOT(slotWritingNextTrack(int, int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(buffer(int)), this, TQT_SIGNAL(bufferStatus(int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(deviceBuffer(int)), this, TQT_SIGNAL(deviceBuffer(int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(writeSpeed(int, int)), this, TQT_SIGNAL(writeSpeed(int, int)) ); connect( d->cdrecordWriter, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotWriterFinished(bool)) ); // connect( d->cdrecordWriter, TQT_SIGNAL(newTask(const TQString&)), this, TQT_SIGNAL(newTask(const TQString&)) ); connect( d->cdrecordWriter, TQT_SIGNAL(newSubTask(const TQString&)), this, TQT_SIGNAL(newSubTask(const TQString&)) ); connect( d->cdrecordWriter, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)), this, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)) ); } d->cdrecordWriter->setBurnDevice( m_writerDevice ); d->cdrecordWriter->clearArguments(); d->cdrecordWriter->setSimulate( m_simulate ); d->cdrecordWriter->setBurnSpeed( m_speed ); // create the cdrecord arguments if( d->currentWrittenSession == 1 && d->toc[0].type() == K3bDevice::Track::AUDIO ) { // // Audio session // if( !d->infFileWriter ) d->infFileWriter = new K3bInfFileWriter(); // // create the inf files if not already done // if( d->infNames.isEmpty() || !TQFile::exists( d->infNames[0] ) ) { unsigned int trackNumber = 1; for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { const K3bDevice::Track& track = *it; if( track.type() == K3bDevice::Track::DATA ) break; d->infFileWriter->setTrack( track ); d->infFileWriter->setTrackNumber( trackNumber ); if( d->haveCddb ) { d->infFileWriter->setTrackTitle( d->cddbInfo.titles[trackNumber-1] ); d->infFileWriter->setTrackPerformer( d->cddbInfo.artists[trackNumber-1] ); d->infFileWriter->setTrackMessage( d->cddbInfo.extInfos[trackNumber-1] ); d->infFileWriter->setAlbumTitle( d->cddbInfo.cdTitle ); d->infFileWriter->setAlbumPerformer( d->cddbInfo.cdArtist ); } if( m_onTheFly ) { d->infFileWriter->setBigEndian( true ); // we let KTempFile choose a temp file but delete it on our own // the same way we delete them when writing with images // It is important that the files have the ending inf because // cdrecord only checks this KTempFile tmp( TQString(), ".inf" ); d->infNames.append( tmp.name() ); bool success = d->infFileWriter->save( *tmp.textStream() ); tmp.close(); if( !success ) return false; } else { d->infFileWriter->setBigEndian( false ); if( !d->infFileWriter->save( d->infNames[trackNumber-1] ) ) return false; } ++trackNumber; } } // // the inf files are ready and named correctly when writing with images // int usedWritingMode = m_writingMode; if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { // // there are a lot of writers out there which produce coasters // in dao mode if the CD contains pregaps of length 0 (or maybe already != 2 secs?) // bool zeroPregap = false; if( d->numSessions == 1 ) { for( K3bDevice::Toc::const_iterator it = d->toc.begin(); it != d->toc.end(); ++it ) { const K3bDevice::Track& track = *it; if( track.index0() == 0 ) { ++it; if( it != d->toc.end() ) zeroPregap = true; --it; } } } if( zeroPregap && m_writerDevice->supportsRawWriting() ) { if( d->numSessions == 1 ) usedWritingMode = K3b::RAW; else usedWritingMode = K3b::TAO; } else if( m_writerDevice->dao() ) usedWritingMode = K3b::DAO; else if( m_writerDevice->supportsRawWriting() ) usedWritingMode = K3b::RAW; else usedWritingMode = K3b::TAO; } d->cdrecordWriter->setWritingMode( usedWritingMode ); if( d->numSessions > 1 ) d->cdrecordWriter->addArgument( "-multi" ); if( d->haveCddb || d->haveCdText ) { if( usedWritingMode == K3b::TAO ) { emit infoMessage( i18n("It is not possible to write CD-Text in TAO mode."), WARNING ); } else if( d->haveCdText && ( !d->haveCddb || m_preferCdText ) ) { // use the raw CDTEXT data d->cdrecordWriter->setRawCdText( d->cdTextRaw ); } else { // make sure the writer job does not create raw cdtext d->cdrecordWriter->setRawCdText( TQByteArray() ); // cdrecord will use the cdtext data in the inf files d->cdrecordWriter->addArgument( "-text" ); } } d->cdrecordWriter->addArgument( "-useinfo" ); // // add all the audio tracks // d->cdrecordWriter->addArgument( "-audio" )->addArgument( "-shorttrack" ); for( unsigned int i = 0; i < d->infNames.count(); ++i ) { if( m_onTheFly ) d->cdrecordWriter->addArgument( d->infNames[i] ); else d->cdrecordWriter->addArgument( d->imageNames[i] ); } } else { // // Data Session // K3bTrack* track = 0; unsigned int dataTrackIndex = 0; if( d->toc.contentType() == K3bDevice::MIXED ) { track = &d->toc[d->toc.count()-1]; dataTrackIndex = 0; } else { track = &d->toc[d->currentWrittenSession-1]; dataTrackIndex = d->currentWrittenSession-1; } bool multi = d->doNotCloseLastSession || (d->numSessions > 1 && d->currentWrittenSession < d->toc.count()); int usedWritingMode = m_writingMode; if( usedWritingMode == K3b::WRITING_MODE_AUTO ) { // at least the NEC3540a does write 2056 byte sectors only in tao mode. Same for LG4040b // since writing data tracks in TAO mode is no loss let's default to TAO in the case of 2056 byte // sectors (which is when writing xa form1 sectors here) if( m_writerDevice->dao() && d->toc.count() == 1 && !multi && track->mode() == K3bDevice::Track::MODE1 ) usedWritingMode = K3b::DAO; else usedWritingMode = K3b::TAO; } d->cdrecordWriter->setWritingMode( usedWritingMode ); // // all but the last session of a multisession disk are written in multi mode // and every data track has it's own session which we forced above // if( multi ) d->cdrecordWriter->addArgument( "-multi" ); // just to let the reader init if( m_onTheFly ) d->cdrecordWriter->addArgument( "-waiti" ); if( track->mode() == K3bDevice::Track::MODE1 ) d->cdrecordWriter->addArgument( "-data" ); else if( track->mode() == K3bDevice::Track::XA_FORM1 ) d->cdrecordWriter->addArgument( "-xa1" ); else d->cdrecordWriter->addArgument( "-xamix" ); if( m_onTheFly ) { // HACK: if the track is TAO recorded cut the two run-out sectors unsigned long trackLen = track->length().lba(); if( d->dataSessionProbablyTAORecorded.count() > dataTrackIndex && d->dataSessionProbablyTAORecorded[dataTrackIndex] ) trackLen -= 2; if( track->mode() == K3bDevice::Track::MODE1 ) trackLen = trackLen * 2048; else if( track->mode() == K3bDevice::Track::XA_FORM1 ) trackLen = trackLen * 2056; // see k3bdatatrackreader.h else trackLen = trackLen * 2332; // see k3bdatatrackreader.h d->cdrecordWriter->addArgument( TQString("-tsize=%1").arg(trackLen) )->addArgument("-"); } else if( d->toc.contentType() == K3bDevice::MIXED ) d->cdrecordWriter->addArgument( d->imageNames[d->toc.count()-1] ); else d->cdrecordWriter->addArgument( d->imageNames[d->currentWrittenSession-1] ); // clear cd text from previous sessions d->cdrecordWriter->setRawCdText( TQByteArray() ); } // // Finally start the writer // emit burning(true); d->writerRunning = true; d->cdrecordWriter->start(); return true; } // both the readcdreader and the audiosessionreader are connected to this slot void K3bCdCopyJob::slotSessionReaderFinished( bool success ) { d->audioReaderRunning = d->dataReaderRunning = false; if( success ) { if( d->numSessions > 1 ) emit infoMessage( i18n("Successfully read session %1.").arg(d->currentReadSession), SUCCESS ); else emit infoMessage( i18n("Successfully read source disk."), SUCCESS ); if( !m_onTheFly ) { if( d->numSessions > d->currentReadSession ) { d->currentReadSession++; readNextSession(); } else { d->readingSuccessful = true; if( !m_onlyCreateImages ) { if( m_readerDevice == m_writerDevice ) { // eject the media (we do this blocking to know if it worked // becasue if it did not it might happen that k3b overwrites a CD-RW // source) if( !m_readerDevice->eject() ) { blockingInformation( i18n("K3b was unable to eject the source disk. Please do so manually.") ); } } if( !writeNextSession() ) { // nothing is running here... finishJob( d->canceled, d->error ); } } else { finishJob( false, false ); } } } } else { if( !d->canceled ) { emit infoMessage( i18n("Error while reading session %1.").arg(d->currentReadSession), ERROR ); if( m_onTheFly ) d->cdrecordWriter->setSourceUnreadable(true); } finishJob( d->canceled, !d->canceled ); } } void K3bCdCopyJob::slotWriterFinished( bool success ) { emit burning(false); d->writerRunning = false; if( success ) { // // if this was the last written session we need to reset d->currentWrittenSession // and start a new writing if more copies are wanted // if( d->currentWrittenSession < d->numSessions ) { d->currentWrittenSession++; d->currentReadSession++; // reload the media emit newSubTask( i18n("Reloading the medium") ); connect( K3bDevice::reload( m_writerDevice ), TQT_SIGNAL(finished(K3bDevice::DeviceHandler*)), this, TQT_SLOT(slotMediaReloadedForNextSession(K3bDevice::DeviceHandler*)) ); } else { d->doneCopies++; if( !m_simulate && d->doneCopies < m_copies ) { // start next copy K3bDevice::eject( m_writerDevice ); d->currentWrittenSession = 1; d->currentReadSession = 1; if( writeNextSession() ) { if( m_onTheFly ) readNextSession(); } else { // nothing running here... finishJob( d->canceled, d->error ); } } else { finishJob( false, false ); } } } else { // // If we are writing on the fly the reader will also stop when it is not able to write anymore // The error handling will be done only here in that case // // the K3bCdrecordWriter emitted an error message finishJob( d->canceled, !d->canceled ); } } void K3bCdCopyJob::slotMediaReloadedForNextSession( K3bDevice::DeviceHandler* dh ) { if( !dh->success() ) blockingInformation( i18n("Please reload the medium and press 'ok'"), i18n("Unable to close the tray") ); if( !writeNextSession() ) { // nothing is running here... finishJob( d->canceled, d->error ); } else if( m_onTheFly ) readNextSession(); } void K3bCdCopyJob::cleanup() { if( m_onTheFly || !m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful) ) { emit infoMessage( i18n("Removing temporary files."), INFO ); for( TQStringList::iterator it = d->infNames.begin(); it != d->infNames.end(); ++it ) TQFile::remove( *it ); } if( !m_onTheFly && (!m_keepImage || ((d->canceled || d->error) && !d->readingSuccessful)) ) { emit infoMessage( i18n("Removing image files."), INFO ); for( TQStringList::iterator it = d->imageNames.begin(); it != d->imageNames.end(); ++it ) TQFile::remove( *it ); // remove the tempdir created in prepareImageFiles() if( d->deleteTempDir ) { TDEIO::NetAccess::del( KURL::fromPathOrURL(m_tempPath), 0 ); d->deleteTempDir = false; } } } void K3bCdCopyJob::slotReaderProgress( int p ) { if( !m_onTheFly || m_onlyCreateImages ) { int bigParts = ( m_onlyCreateImages ? 1 : (m_simulate ? 2 : m_copies + 1 ) ); double done = (double)p * (double)d->sessionSizes[d->currentReadSession-1] / 100.0; for( unsigned int i = 0; i < d->currentReadSession-1; ++i ) done += (double)d->sessionSizes[i]; emit percent( (int)(100.0*done/(double)d->overallSize/(double)bigParts) ); if( d->dataReaderRunning ) emit subPercent(p); } } void K3bCdCopyJob::slotReaderSubProgress( int p ) { // only if reading an audiosession if( !m_onTheFly || m_onlyCreateImages ) { emit subPercent( p ); } } void K3bCdCopyJob::slotReaderProcessedSize( int p, int pp ) { if( !m_onTheFly ) emit processedSubSize( p, pp ); } void K3bCdCopyJob::slotWriterProgress( int p ) { int bigParts = ( m_simulate ? 1 : m_copies ) + ( m_onTheFly ? 0 : 1 ); long done = ( m_onTheFly ? d->doneCopies : d->doneCopies+1 ) * d->overallSize + (p * d->sessionSizes[d->currentWrittenSession-1] / 100); for( unsigned int i = 0; i < d->currentWrittenSession-1; ++i ) done += d->sessionSizes[i]; emit percent( 100*done/d->overallSize/bigParts ); } void K3bCdCopyJob::slotWritingNextTrack( int t, int tt ) { if( d->toc.contentType() == K3bDevice::MIXED ) { if( d->currentWrittenSession == 1 ) emit newSubTask( i18n("Writing track %1 of %2").arg(t).arg(d->toc.count()) ); else emit newSubTask( i18n("Writing track %1 of %2").arg(d->toc.count()).arg(d->toc.count()) ); } else if( d->numSessions > 1 ) emit newSubTask( i18n("Writing track %1 of %2").arg(d->currentWrittenSession).arg(d->toc.count()) ); else emit newSubTask( i18n("Writing track %1 of %2").arg(t).arg(tt) ); } void K3bCdCopyJob::slotReadingNextTrack( int t, int ) { if( !m_onTheFly || m_onlyCreateImages ) { int track = t; if( d->audioReaderRunning ) track = t; else if( d->toc.contentType() == K3bDevice::MIXED ) track = d->toc.count(); else track = d->currentReadSession; emit newSubTask( i18n("Reading track %1 of %2").arg(track).arg(d->toc.count()) ); } } TQString K3bCdCopyJob::jobDescription() const { if( m_onlyCreateImages ) { return i18n("Creating CD Image"); } else if( m_simulate ) { if( m_onTheFly ) return i18n("Simulating CD Copy On-The-Fly"); else return i18n("Simulating CD Copy"); } else { if( m_onTheFly ) return i18n("Copying CD On-The-Fly"); else return i18n("Copying CD"); } } TQString K3bCdCopyJob::jobDetails() const { return i18n("Creating 1 copy", "Creating %n copies", (m_simulate||m_onlyCreateImages) ? 1 : m_copies ); } void K3bCdCopyJob::finishJob( bool c, bool e ) { if( d->running ) { if( c ) { d->canceled = true; emit canceled(); } if( e ) d->error = true; cleanup(); d->running = false; jobFinished( !(c||e) ); } } #include "k3bcdcopyjob.moc"