File indexing completed on 2024-11-03 07:36:28
0001 /* 0002 SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org> 0003 SPDX-FileCopyrightText: 2009 Michal Malek <michalm@jabster.pl> 0004 SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "k3bverificationjob.h" 0010 0011 #include "k3bdevice.h" 0012 #include "k3bdevicehandler.h" 0013 #include "k3bglobals.h" 0014 #include "k3bdatatrackreader.h" 0015 #include "k3bchecksumpipe.h" 0016 #include "k3biso9660.h" 0017 #include "k3b_i18n.h" 0018 0019 #include <QDebug> 0020 #include <QList> 0021 0022 0023 namespace { 0024 class TrackEntry 0025 { 0026 public: 0027 TrackEntry() 0028 : trackNumber(0) { 0029 } 0030 0031 TrackEntry( int tn, const QByteArray& cs, const K3b::Msf& msf ) 0032 : trackNumber(tn), 0033 checksum(cs), 0034 length(msf) { 0035 } 0036 0037 int trackNumber; 0038 QByteArray checksum; 0039 mutable K3b::Msf length; // it's a cache, let's make it modifiable 0040 }; 0041 0042 typedef QList<TrackEntry> TrackEntries; 0043 0044 class NullSinkChecksumPipe : public K3b::ChecksumPipe 0045 { 0046 protected: 0047 qint64 writeData( const char* data, qint64 max ) override { 0048 ChecksumPipe::writeData( data, max ); 0049 return max; 0050 } 0051 }; 0052 } 0053 0054 0055 class K3b::VerificationJob::Private 0056 { 0057 public: 0058 Private( VerificationJob* job ) 0059 : device(0), 0060 dataTrackReader(0), 0061 q(job){ 0062 } 0063 0064 void reloadMedium(); 0065 Msf trackLength( const TrackEntry& trackEntry ); 0066 0067 bool canceled; 0068 K3b::Device::Device* device; 0069 0070 K3b::Msf grownSessionSize; 0071 0072 TrackEntries trackEntries; 0073 TrackEntries::const_iterator currentTrackEntry; 0074 0075 K3b::Device::DiskInfo diskInfo; 0076 K3b::Device::Toc toc; 0077 0078 K3b::DataTrackReader* dataTrackReader; 0079 0080 K3b::Msf currentTrackSize; 0081 K3b::Msf totalSectors; 0082 K3b::Msf alreadyReadSectors; 0083 0084 NullSinkChecksumPipe pipe; 0085 0086 bool readSuccessful; 0087 0088 bool mediumHasBeenReloaded; 0089 0090 VerificationJob* q; 0091 }; 0092 0093 0094 void K3b::VerificationJob::Private::reloadMedium() 0095 { 0096 #ifdef _GNUC_ 0097 #warning FIXME: loks like the reload does not work 0098 #endif 0099 // many drives need to reload the medium to return to a proper state 0100 mediumHasBeenReloaded = true; 0101 emit q->infoMessage( i18n( "Need to reload medium to return to proper state." ), MessageInfo ); 0102 QObject::connect( K3b::Device::sendCommand( Device::DeviceHandler::CommandReload|Device::DeviceHandler::CommandMediaInfo, device ), 0103 SIGNAL(finished(K3b::Device::DeviceHandler*)), 0104 q, 0105 SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); 0106 } 0107 0108 0109 K3b::Msf K3b::VerificationJob::Private::trackLength( const TrackEntry& trackEntry ) 0110 { 0111 K3b::Msf& trackSize = trackEntry.length; 0112 const int& trackNum = trackEntry.trackNumber; 0113 0114 K3b::Device::Track& track = toc[trackNum-1]; 0115 0116 if( trackSize == 0 ) { 0117 trackSize = track.length(); 0118 0119 if( diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) ) { 0120 K3b::Iso9660 isoF( device, track.firstSector().lba() ); 0121 if( isoF.open() ) { 0122 trackSize = isoF.primaryDescriptor().volumeSpaceSize; 0123 } 0124 else { 0125 emit q->infoMessage( i18n("Unable to determine the ISO 9660 filesystem size."), MessageError ); 0126 return 0; 0127 } 0128 } 0129 0130 // 0131 // A data track recorded in TAO mode has two run-out blocks which cannot be read and contain 0132 // zero data anyway. The problem is that I do not know of a valid method to determine if a track 0133 // was written in TAO (the control nibble does definitely not work, I never saw one which did not 0134 // equal 4). 0135 // So the solution for now is to simply try to read the last sector of a data track. If this is not 0136 // possible we assume it was written in TAO mode and reduce the length by 2 sectors 0137 // 0138 if( track.type() == K3b::Device::Track::TYPE_DATA && 0139 diskInfo.mediaType() & K3b::Device::MEDIA_CD_ALL ) { 0140 // we try twice just to be sure 0141 unsigned char buffer[2048]; 0142 if( !device->read10( buffer, 2048, track.lastSector().lba(), 1 ) && 0143 !device->read10( buffer, 2048, track.lastSector().lba(), 1 ) ) { 0144 trackSize -= 2; 0145 qDebug() << "(K3b::CdCopyJob) track " << trackNum << " probably TAO recorded."; 0146 } 0147 } 0148 } 0149 0150 return trackSize; 0151 } 0152 0153 0154 K3b::VerificationJob::VerificationJob( K3b::JobHandler* hdl, QObject* parent ) 0155 : K3b::Job( hdl, parent ) 0156 { 0157 d = new Private( this ); 0158 d->currentTrackEntry = d->trackEntries.constEnd(); 0159 } 0160 0161 0162 K3b::VerificationJob::~VerificationJob() 0163 { 0164 delete d; 0165 } 0166 0167 0168 void K3b::VerificationJob::cancel() 0169 { 0170 d->canceled = true; 0171 if( d->dataTrackReader && d->dataTrackReader->active() ) { 0172 d->dataTrackReader->cancel(); 0173 } 0174 else if( active() ) { 0175 emit canceled(); 0176 jobFinished( false ); 0177 } 0178 } 0179 0180 0181 void K3b::VerificationJob::addTrack( int trackNum, const QByteArray& checksum, const K3b::Msf& length ) 0182 { 0183 d->trackEntries.append( TrackEntry( trackNum, checksum, length ) ); 0184 } 0185 0186 0187 void K3b::VerificationJob::clear() 0188 { 0189 d->trackEntries.clear(); 0190 d->grownSessionSize = 0; 0191 } 0192 0193 0194 void K3b::VerificationJob::setDevice( K3b::Device::Device* dev ) 0195 { 0196 d->device = dev; 0197 } 0198 0199 0200 void K3b::VerificationJob::setGrownSessionSize( const K3b::Msf& s ) 0201 { 0202 d->grownSessionSize = s; 0203 } 0204 0205 0206 void K3b::VerificationJob::start() 0207 { 0208 jobStarted(); 0209 0210 d->canceled = false; 0211 d->alreadyReadSectors = 0; 0212 0213 waitForMedium( d->device, 0214 K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, 0215 K3b::Device::MEDIA_WRITABLE ); 0216 0217 // make sure the job is initialized 0218 if( !d->trackEntries.isEmpty() ) { 0219 d->currentTrackEntry = d->trackEntries.constBegin(); 0220 } 0221 else { 0222 emit infoMessage( i18n( "Internal Error: Verification job improperly initialized (%1)", 0223 i18n("no tracks added") ), MessageError ); 0224 jobFinished( false ); 0225 return; 0226 } 0227 0228 emit newTask( i18n("Checking medium") ); 0229 0230 d->mediumHasBeenReloaded = false; 0231 connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CommandLoad, d->device ), 0232 SIGNAL(finished(K3b::Device::DeviceHandler*)), 0233 this, 0234 SLOT(slotMediaLoaded()) ); 0235 } 0236 0237 0238 void K3b::VerificationJob::slotMediaLoaded() 0239 { 0240 // we always need to wait for the medium. Otherwise the diskinfo below 0241 // may run before the drive is ready! 0242 waitForMedium( d->device, 0243 K3b::Device::STATE_COMPLETE|K3b::Device::STATE_INCOMPLETE, 0244 K3b::Device::MEDIA_WRITABLE ); 0245 0246 connect( K3b::Device::sendCommand( K3b::Device::DeviceHandler::CommandMediaInfo, d->device ), 0247 SIGNAL(finished(K3b::Device::DeviceHandler*)), 0248 this, 0249 SLOT(slotDiskInfoReady(K3b::Device::DeviceHandler*)) ); 0250 } 0251 0252 0253 void K3b::VerificationJob::slotDiskInfoReady( K3b::Device::DeviceHandler* dh ) 0254 { 0255 if( d->canceled ) { 0256 // signal already emitted in cancel() 0257 return; 0258 } 0259 0260 if ( !dh->success() ) { 0261 blockingInformation( i18n("Please reload the medium and press 'OK'"), 0262 i18n("Failed to reload the medium") ); 0263 } 0264 0265 d->diskInfo = dh->diskInfo(); 0266 d->toc = dh->toc(); 0267 d->totalSectors = 0; 0268 0269 // just to be sure check if we actually have all the tracks 0270 for( TrackEntries::iterator it = d->trackEntries.begin(); it != d->trackEntries.end(); ++it ) { 0271 0272 // 0 means "last track" 0273 if( it->trackNumber == 0 ) 0274 it->trackNumber = d->toc.count(); 0275 0276 if( it->trackNumber <= 0 || it->trackNumber > d->toc.count() ) { 0277 if ( d->mediumHasBeenReloaded ) { 0278 emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)", 0279 i18n("specified track number '%1' not found on medium", it->trackNumber) ), MessageError ); 0280 jobFinished( false ); 0281 return; 0282 } 0283 else { 0284 d->reloadMedium(); 0285 return; 0286 } 0287 } 0288 0289 d->totalSectors += d->trackLength( *it ); 0290 } 0291 0292 Q_ASSERT( d->currentTrackEntry != d->trackEntries.constEnd() ); 0293 0294 if( d->currentTrackEntry->trackNumber >= d->toc.count() ) { 0295 readTrack(); 0296 } 0297 else if( !d->mediumHasBeenReloaded ) { 0298 d->reloadMedium(); 0299 } 0300 else { 0301 emit infoMessage( i18n("Internal Error: Verification job improperly initialized (%1)", 0302 i18n("specified track number '%1' not found on medium", d->currentTrackEntry->trackNumber) ), MessageError ); 0303 jobFinished( false ); 0304 } 0305 } 0306 0307 0308 void K3b::VerificationJob::readTrack() 0309 { 0310 if( d->currentTrackEntry == d->trackEntries.constEnd() ) { 0311 jobFinished(true); 0312 return; 0313 } 0314 0315 d->readSuccessful = true; 0316 0317 d->currentTrackSize = d->trackLength( *d->currentTrackEntry ); 0318 if( d->currentTrackSize == 0 ) { 0319 jobFinished(false); 0320 return; 0321 } 0322 0323 emit newTask( i18n("Verifying track %1", d->currentTrackEntry->trackNumber ) ); 0324 0325 K3b::Device::Track& track = d->toc[ d->currentTrackEntry->trackNumber-1 ]; 0326 0327 d->pipe.open(); 0328 0329 if( track.type() == K3b::Device::Track::TYPE_DATA ) { 0330 if( !d->dataTrackReader ) { 0331 d->dataTrackReader = new K3b::DataTrackReader( this ); 0332 connect( d->dataTrackReader, SIGNAL(percent(int)), this, SLOT(slotReaderProgress(int)) ); 0333 connect( d->dataTrackReader, SIGNAL(finished(bool)), this, SLOT(slotReaderFinished(bool)) ); 0334 connect( d->dataTrackReader, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) ); 0335 connect( d->dataTrackReader, SIGNAL(newTask(QString)), this, SIGNAL(newSubTask(QString)) ); 0336 connect( d->dataTrackReader, SIGNAL(debuggingOutput(QString,QString)), 0337 this, SIGNAL(debuggingOutput(QString,QString)) ); 0338 } 0339 0340 d->dataTrackReader->setDevice( d->device ); 0341 d->dataTrackReader->setIgnoreErrors( false ); 0342 d->dataTrackReader->setSectorSize( K3b::DataTrackReader::MODE1 ); 0343 d->dataTrackReader->writeTo( &d->pipe ); 0344 0345 // in case a session was grown the track size does not say anything about the verification data size 0346 if( d->diskInfo.mediaType() & (K3b::Device::MEDIA_DVD_PLUS_RW|K3b::Device::MEDIA_DVD_RW_OVWR) && 0347 d->grownSessionSize > 0 ) { 0348 K3b::Iso9660 isoF( d->device ); 0349 if( isoF.open() ) { 0350 int firstSector = isoF.primaryDescriptor().volumeSpaceSize - d->grownSessionSize.lba(); 0351 d->dataTrackReader->setSectorRange( firstSector, 0352 isoF.primaryDescriptor().volumeSpaceSize -1 ); 0353 } 0354 else { 0355 emit infoMessage( i18n("Unable to determine the ISO 9660 filesystem size."), MessageError ); 0356 jobFinished( false ); 0357 return; 0358 } 0359 } 0360 else 0361 d->dataTrackReader->setSectorRange( track.firstSector(), 0362 track.firstSector() + d->currentTrackSize -1 ); 0363 0364 d->pipe.open(); 0365 d->dataTrackReader->start(); 0366 } 0367 else { 0368 // FIXME: handle audio tracks 0369 } 0370 } 0371 0372 0373 void K3b::VerificationJob::slotReaderProgress( int p ) 0374 { 0375 emit subPercent( p ); 0376 0377 emit percent( 100 * ( d->alreadyReadSectors.lba() + ( p*d->currentTrackSize.lba()/100 ) ) / d->totalSectors.lba() ); 0378 } 0379 0380 0381 void K3b::VerificationJob::slotReaderFinished( bool success ) 0382 { 0383 d->readSuccessful = success; 0384 if( d->readSuccessful && !d->canceled ) { 0385 d->alreadyReadSectors += d->trackLength( *d->currentTrackEntry ); 0386 0387 d->pipe.close(); 0388 0389 // compare the two sums 0390 if( d->currentTrackEntry->checksum != d->pipe.checksum() ) { 0391 emit infoMessage( i18n("Written data in track %1 differs from original.", d->currentTrackEntry->trackNumber), MessageError ); 0392 jobFinished(false); 0393 } 0394 else { 0395 emit infoMessage( i18n("Written data verified."), MessageSuccess ); 0396 0397 ++d->currentTrackEntry; 0398 if( d->currentTrackEntry != d->trackEntries.constEnd() ) 0399 readTrack(); 0400 else 0401 jobFinished(true); 0402 } 0403 } 0404 else { 0405 jobFinished( false ); 0406 } 0407 } 0408 0409 #include "moc_k3bverificationjob.cpp"