File indexing completed on 2024-05-05 04:50:57

0001 /*
0002     SPDX-FileCopyrightText: 2009 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 "k3bdvdformattingjob.h"
0009 
0010 #include "k3bglobals.h"
0011 #include "k3bprocess.h"
0012 #include "k3bdevice.h"
0013 #include "k3bdeviceglobals.h"
0014 #include "k3bdevicehandler.h"
0015 #include "k3bdiskinfo.h"
0016 #include "k3bexternalbinmanager.h"
0017 #include "k3bcore.h"
0018 #include "k3bversion.h"
0019 #include "k3bglobalsettings.h"
0020 #include "k3b_i18n.h"
0021 
0022 #include <QDebug>
0023 #include <QRegularExpression>
0024 
0025 #include <errno.h>
0026 #include <string.h>
0027 
0028 
0029 class K3b::DvdFormattingJob::Private
0030 {
0031 public:
0032     Private()
0033         : formattingMode(FormattingComplete),
0034           force(false),
0035           mode(WritingModeAuto),
0036           device(0),
0037           process(0),
0038           dvdFormatBin(0),
0039           lastProgressValue(0),
0040           running(false),
0041           forceNoEject(false) {
0042     }
0043 
0044     FormattingMode formattingMode;
0045     bool force;
0046     int mode;
0047 
0048     Device::Device* device;
0049     Process* process;
0050     const ExternalBin* dvdFormatBin;
0051 
0052     int lastProgressValue;
0053 
0054     bool success;
0055     bool canceled;
0056     bool running;
0057 
0058     bool forceNoEject;
0059 
0060     bool error;
0061 };
0062 
0063 
0064 K3b::DvdFormattingJob::DvdFormattingJob( JobHandler* jh, QObject* parent )
0065     : BurnJob( jh, parent )
0066 {
0067     d = new Private;
0068 }
0069 
0070 
0071 K3b::DvdFormattingJob::~DvdFormattingJob()
0072 {
0073     delete d->process;
0074     delete d;
0075 }
0076 
0077 
0078 K3b::Device::Device* K3b::DvdFormattingJob::writer() const
0079 {
0080     return d->device;
0081 }
0082 
0083 
0084 void K3b::DvdFormattingJob::setForceNoEject( bool b )
0085 {
0086     d->forceNoEject = b;
0087 }
0088 
0089 
0090 QString K3b::DvdFormattingJob::jobDescription() const
0091 {
0092     return i18n("Formatting disc");
0093 }
0094 
0095 
0096 QString K3b::DvdFormattingJob::jobDetails() const
0097 {
0098     if( d->formattingMode == FormattingQuick )
0099         return i18n("Quick Format");
0100     else
0101         return QString();
0102 }
0103 
0104 
0105 void K3b::DvdFormattingJob::start()
0106 {
0107     d->canceled = false;
0108     d->running = true;
0109     d->error = false;
0110 
0111     jobStarted();
0112 
0113     if( !d->device ) {
0114         emit infoMessage( i18n("No device set"), MessageError );
0115         d->running = false;
0116         jobFinished(false);
0117         return;
0118     }
0119 
0120     // FIXME: check the return value
0121     if( K3b::isMounted( d->device ) ) {
0122         emit infoMessage( i18n("Unmounting medium"), MessageInfo );
0123         K3b::unmount( d->device );
0124     }
0125 
0126     //
0127     // first wait for a dvd+rw, dvd-rw, or bd-re
0128     // Be aware that an empty DVD-RW might be reformatted to another writing mode
0129     // so we also wait for empty dvds
0130     //
0131     if( waitForMedium( d->device,
0132                        Device::STATE_COMPLETE|Device::STATE_INCOMPLETE|Device::STATE_EMPTY,
0133                        Device::MEDIA_REWRITABLE_DVD|Device::MEDIA_BD_RE,
0134                        0,
0135                        i18n("Please insert a rewritable DVD or Blu-ray medium into drive<p><b>%1 %2 (%3)</b>.",
0136                             d->device->vendor(),
0137                             d->device->description(),
0138                             d->device->blockDeviceName()) ) == Device::MEDIA_UNKNOWN ) {
0139         emit canceled();
0140         d->running = false;
0141         jobFinished(false);
0142         return;
0143     }
0144 
0145     emit infoMessage( i18n("Checking medium"), MessageInfo );
0146     emit newTask( i18n("Checking medium") );
0147 
0148     connect( Device::sendCommand( Device::DeviceHandler::CommandDiskInfo, d->device ),
0149              SIGNAL(finished(K3b::Device::DeviceHandler*)),
0150              this,
0151              SLOT(slotDeviceHandlerFinished(K3b::Device::DeviceHandler*)) );
0152 }
0153 
0154 
0155 void K3b::DvdFormattingJob::start( const Device::DiskInfo& di )
0156 {
0157     d->canceled = false;
0158     d->running = true;
0159 
0160     jobStarted();
0161 
0162     startFormatting( di );
0163 }
0164 
0165 
0166 void K3b::DvdFormattingJob::cancel()
0167 {
0168     if( d->running ) {
0169         d->canceled = true;
0170         if( d->process )
0171             d->process->terminate();
0172     }
0173     else {
0174         qDebug() << "(K3b::DvdFormattingJob) not running.";
0175     }
0176 }
0177 
0178 
0179 void K3b::DvdFormattingJob::setDevice( Device::Device* dev )
0180 {
0181     d->device = dev;
0182 }
0183 
0184 
0185 void K3b::DvdFormattingJob::setMode( int m )
0186 {
0187     d->mode = m;
0188 }
0189 
0190 
0191 void K3b::DvdFormattingJob::setFormattingMode( FormattingMode mode )
0192 {
0193     d->formattingMode = mode;
0194 }
0195 
0196 
0197 void K3b::DvdFormattingJob::setForce( bool b )
0198 {
0199     d->force = b;
0200 }
0201 
0202 
0203 void K3b::DvdFormattingJob::slotStderrLine( const QString& line )
0204 {
0205 // * BD/DVD±RW/-RAM format utility by <appro@fy.chalmers.se>, version 7.1.
0206 // * 4.7GB DVD-RW media in Sequential mode detected.
0207 // * blanking 100.0|
0208 
0209 // * formatting 100.0|
0210 
0211     emit debuggingOutput( "dvd+rw-format", line );
0212 
0213     // parsing for the -gui mode (since dvd+rw-format 4.6)
0214     int pos = line.indexOf( "blanking" );
0215     if( pos < 0 )
0216         pos = line.indexOf( "formatting" );
0217     static const QRegularExpression digitRx("\\d");
0218     if( pos >= 0 ) {
0219         pos = line.indexOf( digitRx, pos );
0220     }
0221     // parsing for \b\b... stuff
0222     else if( !line.startsWith('*') ) {
0223         pos = line.indexOf( digitRx );
0224     }
0225     else if( line.startsWith( ":-(" ) ) {
0226         if( line.startsWith( ":-( unable to proceed with format" ) ) {
0227             d->error = true;
0228         }
0229     }
0230 
0231     if( pos >= 0 ) {
0232         static const QRegularExpression endPosRx("[^\\d\\.]");
0233         int endPos = line.indexOf( endPosRx, pos ) - 1;
0234         bool ok;
0235         int progress = (int)(line.mid( pos, endPos - pos ).toDouble(&ok));
0236         if( ok ) {
0237             d->lastProgressValue = progress;
0238             emit percent( progress );
0239         }
0240         else {
0241             qDebug() << "(K3b::DvdFormattingJob) parsing error: '" << line.mid( pos, endPos - pos ) << "'";
0242         }
0243     }
0244 }
0245 
0246 
0247 void K3b::DvdFormattingJob::slotProcessFinished( int exitCode, QProcess::ExitStatus exitStatus )
0248 {
0249     if( d->canceled ) {
0250         emit canceled();
0251         d->success = false;
0252     }
0253     else if( exitStatus == QProcess::NormalExit ) {
0254         if( !d->error && (exitCode == 0) ) {
0255             emit infoMessage( i18n("Formatting successfully completed"), Job::MessageSuccess );
0256 
0257             if( d->lastProgressValue < 100 ) {
0258                 emit infoMessage( i18n("Do not be concerned with the progress stopping before 100%."), MessageInfo );
0259                 emit infoMessage( i18n("The formatting will continue in the background during writing."), MessageInfo );
0260             }
0261 
0262             d->success = true;
0263         }
0264         else {
0265             emit infoMessage( i18n("%1 returned an unknown error (code %2).",d->dvdFormatBin->name(), exitCode),
0266                               Job::MessageError );
0267             emit infoMessage( i18n("Please send me an email with the last output."), Job::MessageError );
0268 
0269             d->success = false;
0270         }
0271     }
0272     else {
0273         emit infoMessage( i18n("%1 did not exit cleanly.",d->dvdFormatBin->name()),
0274                           MessageError );
0275         d->success = false;
0276     }
0277 
0278     if( d->forceNoEject ||
0279         !k3bcore->globalSettings()->ejectMedia() ) {
0280         d->running = false;
0281         jobFinished(d->success);
0282     }
0283     else {
0284         emit infoMessage( i18n("Ejecting medium..."), MessageInfo );
0285         connect( Device::eject( d->device ),
0286                  SIGNAL(finished(K3b::Device::DeviceHandler*)),
0287                  this,
0288                  SLOT(slotEjectingFinished(K3b::Device::DeviceHandler*)) );
0289     }
0290 }
0291 
0292 
0293 void K3b::DvdFormattingJob::slotEjectingFinished( Device::DeviceHandler* dh )
0294 {
0295     if( !dh->success() )
0296         emit infoMessage( i18n("Unable to eject medium."), MessageError );
0297 
0298     d->running = false;
0299     jobFinished(d->success);
0300 }
0301 
0302 
0303 void K3b::DvdFormattingJob::slotDeviceHandlerFinished( Device::DeviceHandler* dh )
0304 {
0305     if( d->canceled ) {
0306         emit canceled();
0307         jobFinished(false);
0308         d->running = false;
0309     }
0310 
0311     if( dh->success() ) {
0312         startFormatting( dh->diskInfo() );
0313     }
0314     else {
0315         emit infoMessage( i18n("Unable to determine media state."), MessageError );
0316         d->running = false;
0317         jobFinished(false);
0318     }
0319 }
0320 
0321 
0322 void K3b::DvdFormattingJob::startFormatting( const Device::DiskInfo& diskInfo )
0323 {
0324     //
0325     // Now check the media type:
0326     // if DVD-RW: use d->mode
0327     //            emit warning if formatting is full and stuff
0328     //
0329     // in overwrite mode: emit info that progress might stop before 100% since formatting will continue
0330     //                    in the background once the media gets rewritten (only DVD+RW/BD-RE?)
0331     //
0332 
0333 
0334     // emit info about what kind of media has been found
0335     if( diskInfo.mediaType() & (Device::MEDIA_REWRITABLE_DVD | Device::MEDIA_BD_RE) ) {
0336         emit infoMessage( i18n("Found %1 medium.", Device::mediaTypeString(diskInfo.mediaType())),
0337                         MessageInfo );
0338     }
0339     else {
0340         emit infoMessage( i18n("No rewritable DVD or BD medium found. Unable to format."), MessageError );
0341         d->running = false;
0342         jobFinished(false);
0343         return;
0344     }
0345 
0346     bool format = true;  // do we need to format
0347     bool blank = false;  // blank is for DVD-RW sequential incremental
0348     // DVD-RW restricted overwrite and DVD+RW uses the force option (or no option at all)
0349 
0350     //
0351     // DVD+RW/BD-RE is quite easy to handle. There is only one possible mode and it is always recommended to not
0352     // format it more than once but to overwrite it once it is formatted
0353     // Once the initial formatting has been done it's always "appendable" (or "complete"???)
0354     //
0355 
0356     if( diskInfo.mediaType() & (Device::MEDIA_DVD_PLUS_ALL | Device::MEDIA_BD_RE) ) {
0357 
0358         if( diskInfo.empty() ) {
0359             //
0360             // The DVD+RW/BD is blank and needs to be initially formatted
0361             //
0362             blank = false;
0363         }
0364         else {
0365             emit infoMessage( i18n("No need to format %1 media more than once.",
0366                                    Device::mediaTypeString(diskInfo.mediaType())), MessageInfo );
0367             emit infoMessage( i18n("It may simply be overwritten."), MessageInfo );
0368 
0369             if( d->force ) {
0370                 emit infoMessage( i18n("Forcing formatting anyway."), MessageInfo );
0371                 emit infoMessage( i18n("It is not recommended to force formatting of %1 media.",
0372                                        Device::mediaTypeString(diskInfo.mediaType())), MessageInfo );
0373                 emit infoMessage( i18n("After 10-20 reformats the media might become unusable."), MessageInfo );
0374                 blank = false;
0375             }
0376             else {
0377                 format = false;
0378             }
0379         }
0380 
0381         if( format )
0382             emit newSubTask( i18n("Formatting %1 medium", Device::mediaTypeString(diskInfo.mediaType())) );
0383     }
0384 
0385     //
0386     // DVD-RW has two modes: incremental sequential (the default which is also needed for DAO writing)
0387     // and restricted overwrite which compares to the DVD+RW mode.
0388     //
0389 
0390     else {  // MEDIA_DVD_RW
0391 
0392         if( diskInfo.currentProfile() != Device::MEDIA_UNKNOWN ) {
0393             emit infoMessage( i18n("Formatted in %1 mode.",Device::mediaTypeString(diskInfo.currentProfile())), MessageInfo );
0394 
0395 
0396             //
0397             // Is it possible to have an empty DVD-RW in restricted overwrite mode???? I don't think so.
0398             //
0399 
0400             if( diskInfo.empty() &&
0401                 (d->mode == WritingModeAuto ||
0402                  (d->mode == WritingModeIncrementalSequential &&
0403                   diskInfo.currentProfile() == Device::MEDIA_DVD_RW_SEQ) ||
0404                  (d->mode == WritingModeRestrictedOverwrite &&
0405                   diskInfo.currentProfile() == Device::MEDIA_DVD_RW_OVWR) )
0406                 ) {
0407                 emit infoMessage( i18n("Media is already empty."), MessageInfo );
0408                 if( d->force )
0409                     emit infoMessage( i18n("Forcing formatting anyway."), MessageInfo );
0410                 else
0411                     format = false;
0412             }
0413             else if( diskInfo.currentProfile() == Device::MEDIA_DVD_RW_OVWR &&
0414                      d->mode != WritingModeIncrementalSequential ) {
0415                 emit infoMessage( i18n("No need to format %1 media more than once.",
0416                                   Device::mediaTypeString(diskInfo.currentProfile())), MessageInfo );
0417                 emit infoMessage( i18n("It may simply be overwritten."), MessageInfo );
0418 
0419                 if( d->force )
0420                     emit infoMessage( i18n("Forcing formatting anyway."), MessageInfo );
0421                 else
0422                     format = false;
0423             }
0424 
0425             if( format ) {
0426                 if( d->mode == WritingModeAuto ) {
0427                     // just format in the same mode as the media is currently formatted
0428                     blank = (diskInfo.currentProfile() == Device::MEDIA_DVD_RW_SEQ);
0429                 }
0430                 else {
0431                     blank = (d->mode == WritingModeIncrementalSequential);
0432                 }
0433 
0434                 emit newSubTask( i18n("Formatting"
0435                                       " DVD-RW in %1 mode.",Device::mediaTypeString( blank ?
0436                                                                                      Device::MEDIA_DVD_RW_SEQ :
0437                                                                                      Device::MEDIA_DVD_RW_OVWR )) );
0438             }
0439         }
0440         else {
0441             emit infoMessage( i18n("Unable to determine the current formatting state of the DVD-RW medium."), MessageError );
0442             d->running = false;
0443             jobFinished(false);
0444             return;
0445         }
0446     }
0447 
0448 
0449     if( format ) {
0450         delete d->process;
0451         d->process = new Process();
0452         connect( d->process, SIGNAL(stderrLine(QString)), this, SLOT(slotStderrLine(QString)) );
0453         connect( d->process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessFinished(int,QProcess::ExitStatus)) );
0454 
0455         d->dvdFormatBin = k3bcore->externalBinManager()->binObject( "dvd+rw-format" );
0456         if( !d->dvdFormatBin ) {
0457             emit infoMessage( i18n("Could not find %1 executable.",QString("dvd+rw-format")), MessageError );
0458             d->running = false;
0459             jobFinished(false);
0460             return;
0461         }
0462 
0463         if( !d->dvdFormatBin->copyright().isEmpty() )
0464             emit infoMessage( i18n("Using %1 %2 – Copyright © %3",d->dvdFormatBin->name(),d->dvdFormatBin->version(),d->dvdFormatBin->copyright()), MessageInfo );
0465 
0466 
0467         *d->process << d->dvdFormatBin;
0468 
0469         if( d->dvdFormatBin->version() >= Version( 4, 6 ) )
0470             *d->process << "-gui";
0471 
0472         QString p;
0473         if( blank )
0474             p = "-blank";
0475         else
0476             p = "-force";
0477         if( d->formattingMode == FormattingComplete )
0478             p += "=full";
0479 
0480         *d->process << p;
0481 
0482         *d->process << d->device->blockDeviceName();
0483 
0484         // additional user parameters from config
0485         const QStringList& params = d->dvdFormatBin->userParameters();
0486         for( QStringList::const_iterator it = params.begin(); it != params.end(); ++it )
0487             *d->process << *it;
0488 
0489         qDebug() << "***** dvd+rw-format parameters:\n";
0490         QString s = d->process->joinedArgs();
0491         qDebug() << s << Qt::endl << Qt::flush;
0492         emit debuggingOutput( "dvd+rw-format command:", s );
0493 
0494         if( !d->process->start( KProcess::OnlyStderrChannel ) ) {
0495             // something went wrong when starting the program
0496             // it "should" be the executable
0497             qDebug() << "(K3b::DvdFormattingJob) could not start " << d->dvdFormatBin->path();
0498             emit infoMessage( i18n("Could not start %1.",d->dvdFormatBin->name()), Job::MessageError );
0499             d->running = false;
0500             jobFinished(false);
0501         }
0502         else {
0503             emit newTask( i18n("Formatting") );
0504         }
0505     }
0506     else {
0507         // already formatted :)
0508         d->running = false;
0509         jobFinished(true);
0510     }
0511 }
0512 
0513 #include "moc_k3bdvdformattingjob.cpp"