summaryrefslogtreecommitdiffstats
path: root/libk3b/jobs/k3bverificationjob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libk3b/jobs/k3bverificationjob.cpp')
-rw-r--r--libk3b/jobs/k3bverificationjob.cpp384
1 files changed, 384 insertions, 0 deletions
diff --git a/libk3b/jobs/k3bverificationjob.cpp b/libk3b/jobs/k3bverificationjob.cpp
new file mode 100644
index 0000000..e73530e
--- /dev/null
+++ b/libk3b/jobs/k3bverificationjob.cpp
@@ -0,0 +1,384 @@
+/*
+ *
+ * $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 <qcstring.h>
+#include <qapplication.h>
+#include <qvaluelist.h>
+#include <qpair.h>
+
+
+class K3bVerificationJobTrackEntry
+{
+public:
+ K3bVerificationJobTrackEntry()
+ : trackNumber(0) {
+ }
+
+ K3bVerificationJobTrackEntry( int tn, const QCString& cs, const K3b::Msf& msf )
+ : trackNumber(tn),
+ checksum(cs),
+ length(msf) {
+ }
+
+ int trackNumber;
+ QCString 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;
+
+ QValueList<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, QObject* parent, const char* name )
+ : K3bJob( hdl, parent, name )
+{
+ d = new Private();
+
+ d->md5Job = new K3bMd5Job( this );
+ connect( d->md5Job, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) );
+ connect( d->md5Job, SIGNAL(finished(bool)), this, SLOT(slotMd5JobFinished(bool)) );
+ connect( d->md5Job, SIGNAL(debuggingOutput(const QString&, const QString&)),
+ this, SIGNAL(debuggingOutput(const QString&, const QString&)) );
+}
+
+
+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 QCString& 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 ),
+ SIGNAL(finished(K3bDevice::DeviceHandler*)),
+ this,
+ 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 ),
+ SIGNAL(finished(K3bDevice::DeviceHandler*)),
+ this,
+ 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( QValueList<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 ),
+ SIGNAL(finished(bool)),
+ this,
+ 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, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) );
+ // connect( d->dataTrackReader, SIGNAL(processedSize(int, int)), this, SLOT(slotReaderProcessedSize(int, int)) );
+ connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) );
+ connect( d->dataTrackReader, SIGNAL(infoMessage(const QString&, int)), this, SIGNAL(infoMessage(const QString&, int)) );
+ connect( d->dataTrackReader, SIGNAL(newTask(const QString&)), this, SIGNAL(newSubTask(const QString&)) );
+ connect( d->dataTrackReader, SIGNAL(debuggingOutput(const QString&, const QString&)),
+ this, SIGNAL(debuggingOutput(const QString&, const QString&)) );
+ }
+
+ 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"