|
|
|
/*
|
|
|
|
*
|
|
|
|
* $Id: k3bisoimageverificationjob.cpp 597651 2006-10-21 08:04:01Z trueg $
|
|
|
|
* Copyright (C) 2003-2007 Sebastian Trueg <trueg@k3b.org>
|
|
|
|
*
|
|
|
|
* This file is part of the K3b project.
|
|
|
|
* Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
|
|
|
|
*
|
|
|
|
* 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 "k3bverificationjob.h"
|
|
|
|
|
|
|
|
#include <k3bdevice.h>
|
|
|
|
#include <k3bdevicehandler.h>
|
|
|
|
#include <k3bmd5job.h>
|
|
|
|
#include <k3bglobals.h>
|
|
|
|
#include <k3bdatatrackreader.h>
|
|
|
|
#include <k3bpipe.h>
|
|
|
|
#include <k3biso9660.h>
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kio/global.h>
|
|
|
|
#include <kio/job.h>
|
|
|
|
#include <kio/netaccess.h>
|
|
|
|
|
|
|
|
#include <tqcstring.h>
|
|
|
|
#include <tqapplication.h>
|
|
|
|
#include <tqvaluelist.h>
|
|
|
|
#include <tqpair.h>
|
|
|
|
|
|
|
|
|
|
|
|
class K3bVerificationJobTrackEntry
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
K3bVerificationJobTrackEntry()
|
|
|
|
: trackNumber(0) {
|
|
|
|
}
|
|
|
|
|
|
|
|
K3bVerificationJobTrackEntry( int tn, const TQCString& cs, const K3b::Msf& msf )
|
|
|
|
: trackNumber(tn),
|
|
|
|
checksum(cs),
|
|
|
|
length(msf) {
|
|
|
|
}
|
|
|
|
|
|
|
|
int trackNumber;
|
|
|
|
TQCString checksum;
|
|
|
|
K3b::Msf length;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class K3bVerificationJob::Private
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Private()
|
|
|
|
: md5Job(0),
|
|
|
|
device(0),
|
|
|
|
dataTrackReader(0) {
|
|
|
|
}
|
|
|
|
|
|
|
|
bool canceled;
|
|
|
|
K3bMd5Job* md5Job;
|
|
|
|
K3bDevice::Device* device;
|
|
|
|
|
|
|
|
K3b::Msf grownSessionSize;
|
|
|
|
|
|
|
|
TQValueList<K3bVerificationJobTrackEntry> tracks;
|
|
|
|
int currentTrackIndex;
|
|
|
|
|
|
|
|
K3bDevice::DiskInfo diskInfo;
|
|
|
|
K3bDevice::Toc toc;
|
|
|
|
|
|
|
|
K3bDataTrackReader* dataTrackReader;
|
|
|
|
|
|
|
|
K3b::Msf currentTrackSize;
|
|
|
|
K3b::Msf totalSectors;
|
|
|
|
K3b::Msf alreadyReadSectors;
|
|
|
|
|
|
|
|
K3bPipe pipe;
|
|
|
|
|
|
|
|
bool readSuccessful;
|
|
|
|
|
|
|
|
bool mediumHasBeenReloaded;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
K3bVerificationJob::K3bVerificationJob( K3bJobHandler* hdl, TQObject* parent, const char* name )
|
|
|
|
: K3bJob( hdl, parent, name )
|
|
|
|
{
|
|
|
|
d = new Private();
|
|
|
|
|
|
|
|
d->md5Job = new K3bMd5Job( this );
|
|
|
|
connect( d->md5Job, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) );
|
|
|
|
connect( d->md5Job, TQT_SIGNAL(finished(bool)), this, TQT_SLOT(slotMd5JobFinished(bool)) );
|
|
|
|
connect( d->md5Job, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)),
|
|
|
|
this, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
K3bVerificationJob::~K3bVerificationJob()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::cancel()
|
|
|
|
{
|
|
|
|
d->canceled = true;
|
|
|
|
if( d->md5Job && d->md5Job->active() )
|
|
|
|
d->md5Job->cancel();
|
|
|
|
if( d->dataTrackReader && d->dataTrackReader->active() )
|
|
|
|
d->dataTrackReader->cancel();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::addTrack( int trackNum, const TQCString& checksum, const K3b::Msf& length )
|
|
|
|
{
|
|
|
|
d->tracks.append( K3bVerificationJobTrackEntry( trackNum, checksum, length ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::clear()
|
|
|
|
{
|
|
|
|
d->tracks.clear();
|
|
|
|
d->grownSessionSize = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::setDevice( K3bDevice::Device* dev )
|
|
|
|
{
|
|
|
|
d->device = dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::setGrownSessionSize( const K3b::Msf& s )
|
|
|
|
{
|
|
|
|
d->grownSessionSize = s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::start()
|
|
|
|
{
|
|
|
|
jobStarted();
|
|
|
|
|
|
|
|
d->canceled = false;
|
|
|
|
d->currentTrackIndex = 0;
|
|
|
|
d->alreadyReadSectors = 0;
|
|
|
|
|
|
|
|
emit newTask( i18n("Checking medium") );
|
|
|
|
|
|
|
|
d->mediumHasBeenReloaded = false;
|
|
|
|
connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::DISKINFO, d->device ),
|
|
|
|
TQT_SIGNAL(finished(K3bDevice::DeviceHandler*)),
|
|
|
|
this,
|
|
|
|
TQT_SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::slotMediaReloaded( bool /*success*/ )
|
|
|
|
{
|
|
|
|
// we always need to wait for the medium. Otherwise the diskinfo below
|
|
|
|
// may run before the drive is ready!
|
|
|
|
waitForMedia( d->device,
|
|
|
|
K3bDevice::STATE_COMPLETE|K3bDevice::STATE_INCOMPLETE,
|
|
|
|
K3bDevice::MEDIA_WRITABLE );
|
|
|
|
|
|
|
|
d->mediumHasBeenReloaded = true;
|
|
|
|
|
|
|
|
emit newTask( i18n("Checking medium") );
|
|
|
|
|
|
|
|
connect( K3bDevice::sendCommand( K3bDevice::DeviceHandler::DISKINFO, d->device ),
|
|
|
|
TQT_SIGNAL(finished(K3bDevice::DeviceHandler*)),
|
|
|
|
this,
|
|
|
|
TQT_SLOT(slotDiskInfoReady(K3bDevice::DeviceHandler*)) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::slotDiskInfoReady( K3bDevice::DeviceHandler* dh )
|
|
|
|
{
|
|
|
|
if( d->canceled ) {
|
|
|
|
emit canceled();
|
|
|
|
jobFinished(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
d->diskInfo = dh->diskInfo();
|
|
|
|
d->toc = dh->toc();
|
|
|
|
d->totalSectors = 0;
|
|
|
|
|
|
|
|
// just to be sure check if we actually have all the tracks
|
|
|
|
int i = 0;
|
|
|
|
for( TQValueList<K3bVerificationJobTrackEntry>::iterator it = d->tracks.begin();
|
|
|
|
it != d->tracks.end(); ++i, ++it ) {
|
|
|
|
|
|
|
|
// 0 means "last track"
|
|
|
|
if( (*it).trackNumber == 0 )
|
|
|
|
(*it).trackNumber = d->toc.count();
|
|
|
|
|
|
|
|
if( (int)d->toc.count() < (*it).trackNumber ) {
|
|
|
|
if ( d->mediumHasBeenReloaded ) {
|
|
|
|
emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)")
|
|
|
|
.arg( "Specified track number not found on medium" ), ERROR );
|
|
|
|
jobFinished( false );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// many drives need to reload the medium to return to a proper state
|
|
|
|
emit newTask( i18n("Reloading the medium") );
|
|
|
|
connect( K3bDevice::reload( d->device ),
|
|
|
|
TQT_SIGNAL(finished(bool)),
|
|
|
|
this,
|
|
|
|
TQT_SLOT(slotMediaReloaded(bool)) );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
d->totalSectors += trackLength( i );
|
|
|
|
}
|
|
|
|
|
|
|
|
readTrack( 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::readTrack( int trackIndex )
|
|
|
|
{
|
|
|
|
d->currentTrackIndex = trackIndex;
|
|
|
|
d->readSuccessful = true;
|
|
|
|
|
|
|
|
d->currentTrackSize = trackLength( trackIndex );
|
|
|
|
if( d->currentTrackSize == 0 ) {
|
|
|
|
jobFinished(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit newTask( i18n("Verifying track %1").arg( d->tracks[trackIndex].trackNumber ) );
|
|
|
|
|
|
|
|
d->pipe.open();
|
|
|
|
|
|
|
|
if( d->toc[d->tracks[trackIndex].trackNumber-1].type() == K3bDevice::Track::DATA ) {
|
|
|
|
if( !d->dataTrackReader ) {
|
|
|
|
d->dataTrackReader = new K3bDataTrackReader( 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(slotReaderFinished(bool)) );
|
|
|
|
connect( d->dataTrackReader, TQT_SIGNAL(infoMessage(const TQString&, int)), this, TQT_SIGNAL(infoMessage(const TQString&, int)) );
|
|
|
|
connect( d->dataTrackReader, TQT_SIGNAL(newTask(const TQString&)), this, TQT_SIGNAL(newSubTask(const TQString&)) );
|
|
|
|
connect( d->dataTrackReader, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)),
|
|
|
|
this, TQT_SIGNAL(debuggingOutput(const TQString&, const TQString&)) );
|
|
|
|
}
|
|
|
|
|
|
|
|
d->dataTrackReader->setDevice( d->device );
|
|
|
|
d->dataTrackReader->setIgnoreErrors( false );
|
|
|
|
d->dataTrackReader->setSectorSize( K3bDataTrackReader::MODE1 );
|
|
|
|
|
|
|
|
// in case a session was grown the track size does not say anything about the verification data size
|
|
|
|
if( d->diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) &&
|
|
|
|
d->grownSessionSize > 0 ) {
|
|
|
|
K3bIso9660 isoF( d->device );
|
|
|
|
if( isoF.open() ) {
|
|
|
|
int firstSector = isoF.primaryDescriptor().volumeSpaceSize - d->grownSessionSize.lba();
|
|
|
|
d->dataTrackReader->setSectorRange( firstSector,
|
|
|
|
isoF.primaryDescriptor().volumeSpaceSize -1 );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR );
|
|
|
|
jobFinished( false );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
d->dataTrackReader->setSectorRange( d->toc[d->tracks[trackIndex].trackNumber-1].firstSector(),
|
|
|
|
d->toc[d->tracks[trackIndex].trackNumber-1].firstSector() + d->currentTrackSize -1 );
|
|
|
|
|
|
|
|
d->md5Job->setMaxReadSize( d->currentTrackSize.mode1Bytes() );
|
|
|
|
|
|
|
|
d->dataTrackReader->writeToFd( d->pipe.in() );
|
|
|
|
d->dataTrackReader->start();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// FIXME: handle audio tracks
|
|
|
|
}
|
|
|
|
|
|
|
|
d->md5Job->setFd( d->pipe.out() );
|
|
|
|
d->md5Job->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::slotReaderProgress( int p )
|
|
|
|
{
|
|
|
|
emit subPercent( p );
|
|
|
|
|
|
|
|
emit percent( 100 * ( d->alreadyReadSectors.lba() + ( p*d->currentTrackSize.lba()/100 ) ) / d->totalSectors.lba() );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::slotMd5JobFinished( bool success )
|
|
|
|
{
|
|
|
|
d->pipe.close();
|
|
|
|
|
|
|
|
if( success && !d->canceled && d->readSuccessful ) {
|
|
|
|
// compare the two sums
|
|
|
|
if( d->tracks[d->currentTrackIndex].checksum != d->md5Job->hexDigest() ) {
|
|
|
|
emit infoMessage( i18n("Written data in track %1 differs from original.").arg(d->tracks[d->currentTrackIndex].trackNumber), ERROR );
|
|
|
|
jobFinished(false);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
emit infoMessage( i18n("Written data verified."), SUCCESS );
|
|
|
|
++d->currentTrackIndex;
|
|
|
|
if( d->currentTrackIndex < (int)d->tracks.count() )
|
|
|
|
readTrack( d->currentTrackIndex );
|
|
|
|
else
|
|
|
|
jobFinished(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// The md5job emitted an error message. So there is no need to do this again
|
|
|
|
jobFinished(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void K3bVerificationJob::slotReaderFinished( bool success )
|
|
|
|
{
|
|
|
|
d->readSuccessful = success;
|
|
|
|
if( !d->readSuccessful )
|
|
|
|
d->md5Job->cancel();
|
|
|
|
else {
|
|
|
|
d->alreadyReadSectors += trackLength( d->currentTrackIndex );
|
|
|
|
|
|
|
|
// close the pipe and let the md5 job finish gracefully
|
|
|
|
d->pipe.closeIn();
|
|
|
|
// d->md5Job->stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
K3b::Msf K3bVerificationJob::trackLength( int trackIndex )
|
|
|
|
{
|
|
|
|
K3b::Msf& trackSize = d->tracks[trackIndex].length;
|
|
|
|
const int& trackNum = d->tracks[trackIndex].trackNumber;
|
|
|
|
|
|
|
|
if( trackSize == 0 ) {
|
|
|
|
trackSize = d->toc[trackNum-1].length();
|
|
|
|
|
|
|
|
if( d->diskInfo.mediaType() & (K3bDevice::MEDIA_DVD_PLUS_RW|K3bDevice::MEDIA_DVD_RW_OVWR) ) {
|
|
|
|
K3bIso9660 isoF( d->device, d->toc[trackNum-1].firstSector().lba() );
|
|
|
|
if( isoF.open() ) {
|
|
|
|
trackSize = isoF.primaryDescriptor().volumeSpaceSize;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
emit infoMessage( i18n("Unable to determine the ISO9660 filesystem size."), ERROR );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
//
|
|
|
|
if( d->toc[trackNum-1].type() == K3bDevice::Track::DATA &&
|
|
|
|
d->diskInfo.mediaType() & K3bDevice::MEDIA_CD_ALL ) {
|
|
|
|
// we try twice just to be sure
|
|
|
|
unsigned char buffer[2048];
|
|
|
|
if( !d->device->read10( buffer, 2048, d->toc[trackNum-1].lastSector().lba(), 1 ) &&
|
|
|
|
!d->device->read10( buffer, 2048, d->toc[trackNum-1].lastSector().lba(), 1 ) ) {
|
|
|
|
trackSize -= 2;
|
|
|
|
kdDebug() << "(K3bCdCopyJob) track " << trackNum << " probably TAO recorded." << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return trackSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "k3bverificationjob.moc"
|