1 /*
2     SPDX-FileCopyrightText: 2003-2009 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2011 Michal Malek <michalm@jabster.pl>
4     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "k3bclonejob.h"
10 
11 #include "k3breadcdreader.h"
12 #include "k3bcdrecordwriter.h"
13 #include "k3bexternalbinmanager.h"
14 #include "k3bdevice.h"
15 #include "k3bdevicehandler.h"
16 #include "k3bglobals.h"
17 #include "k3bcore.h"
18 #include "k3bclonetocreader.h"
19 #include "k3bglobalsettings.h"
20 #include "k3b_i18n.h"
21 
22 #include <QDebug>
23 #include <QFile>
24 #include <QFileInfo>
25 
26 
27 
28 class K3b::CloneJob::Private
29 {
30 public:
Private()31     Private()
32         : doneCopies(0) {
33     }
34 
35     int doneCopies;
36 };
37 
38 
CloneJob(K3b::JobHandler * hdl,QObject * parent)39 K3b::CloneJob::CloneJob( K3b::JobHandler* hdl, QObject* parent )
40     : K3b::BurnJob( hdl, parent ),
41       m_writerDevice(0),
42       m_readerDevice(0),
43       m_writerJob(0),
44       m_readcdReader(0),
45       m_removeImageFiles(false),
46       m_canceled(false),
47       m_running(false),
48       m_simulate(false),
49       m_speed(1),
50       m_copies(1),
51       m_onlyCreateImage(false),
52       m_onlyBurnExistingImage(false),
53       m_readRetries(128)
54 {
55     d = new Private;
56 }
57 
58 
~CloneJob()59 K3b::CloneJob::~CloneJob()
60 {
61     delete d;
62 }
63 
64 
start()65 void K3b::CloneJob::start()
66 {
67     jobStarted();
68 
69     m_canceled = false;
70     m_running = true;
71 
72 
73     // TODO: check the cd size and warn the user if not enough space
74 
75     //
76     // We first check if cdrecord has clone support
77     // The readcdReader will check the same for readcd
78     //
79     const K3b::ExternalBin* cdrecordBin = k3bcore->externalBinManager()->binObject( "cdrecord" );
80     if( !cdrecordBin ) {
81         emit infoMessage( i18n("Could not find %1 executable.",QString("cdrecord")), MessageError );
82         jobFinished(false);
83         m_running = false;
84         return;
85     }
86     else if( !cdrecordBin->hasFeature( "clone" ) ) {
87         emit infoMessage( i18n("Cdrecord version %1 does not have cloning support.",cdrecordBin->version()), MessageError );
88         jobFinished(false);
89         m_running = false;
90         return;
91     }
92 
93     if( (!m_onlyCreateImage && !writer()) ||
94         (!m_onlyBurnExistingImage && !readingDevice()) ) {
95         emit infoMessage( i18n("No device set."), MessageError );
96         jobFinished(false);
97         m_running = false;
98         return;
99     }
100 
101     if( !m_onlyCreateImage ) {
102         if( !writer()->supportsWritingMode( K3b::Device::WRITINGMODE_RAW_R96R ) &&
103             !writer()->supportsWritingMode( K3b::Device::WRITINGMODE_RAW_R16 ) ) {
104             emit infoMessage( i18n("CD writer %1 (%2) does not support cloning.",
105                                    writer()->vendor(),
106                                    writer()->description()), MessageError );
107             m_running = false;
108             jobFinished(false);
109             return;
110         }
111     }
112 
113     if( m_imagePath.isEmpty() ) {
114         m_imagePath = K3b::findTempFile( "img" );
115     }
116     else if( QFileInfo(m_imagePath).isDir() ) {
117         m_imagePath = K3b::findTempFile( "img", m_imagePath );
118     }
119 
120     if( m_onlyBurnExistingImage ) {
121         startWriting();
122     }
123     else {
124         emit burning( false );
125 
126         prepareReader();
127 
128         if( waitForMedium( readingDevice(),
129                           K3b::Device::STATE_COMPLETE,
130                           K3b::Device::MEDIA_WRITABLE_CD|K3b::Device::MEDIA_CD_ROM ) == Device::MEDIA_UNKNOWN ) {
131             m_running = false;
132             emit canceled();
133             jobFinished(false);
134             return;
135         }
136 
137         emit newTask( i18n("Reading clone image") );
138 
139         m_readcdReader->start();
140     }
141 }
142 
143 
prepareReader()144 void K3b::CloneJob::prepareReader()
145 {
146     if( !m_readcdReader ) {
147         m_readcdReader = new K3b::ReadcdReader( this, this );
148         connect( m_readcdReader, SIGNAL(percent(int)), this, SLOT(slotReadingPercent(int)) );
149         connect( m_readcdReader, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) );
150         connect( m_readcdReader, SIGNAL(processedSize(int,int)), this, SIGNAL(processedSubSize(int,int)) );
151         connect( m_readcdReader, SIGNAL(finished(bool)), this, SLOT(slotReadingFinished(bool)) );
152         connect( m_readcdReader, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
153         connect( m_readcdReader, SIGNAL(newTask(QString)), this, SIGNAL(newSubTask(QString)) );
154         connect( m_readcdReader, SIGNAL(debuggingOutput(QString,QString)),
155                  this, SIGNAL(debuggingOutput(QString,QString)) );
156     }
157 
158     m_readcdReader->setReadDevice( readingDevice() );
159     m_readcdReader->setReadSpeed( 0 ); // MAX
160     m_readcdReader->setDisableCorrection( m_noCorrection );
161     m_readcdReader->setImagePath( m_imagePath );
162     m_readcdReader->setClone( true );
163     m_readcdReader->setRetries( m_readRetries );
164 }
165 
166 
prepareWriter()167 void K3b::CloneJob::prepareWriter()
168 {
169     if( !m_writerJob ) {
170         m_writerJob = new K3b::CdrecordWriter( writer(), this, this );
171         connect( m_writerJob, SIGNAL(infoMessage(QString,int)), this, SIGNAL(infoMessage(QString,int)) );
172         connect( m_writerJob, SIGNAL(percent(int)), this, SLOT(slotWriterPercent(int)) );
173         connect( m_writerJob, SIGNAL(percent(int)), this, SIGNAL(subPercent(int)) );
174         connect( m_writerJob, SIGNAL(nextTrack(int,int)), this, SLOT(slotWriterNextTrack(int,int)) );
175         connect( m_writerJob, SIGNAL(processedSize(int,int)), this, SIGNAL(processedSubSize(int,int)) );
176         connect( m_writerJob, SIGNAL(buffer(int)), this, SIGNAL(bufferStatus(int)) );
177         connect( m_writerJob, SIGNAL(deviceBuffer(int)), this, SIGNAL(deviceBuffer(int)) );
178         connect( m_writerJob, SIGNAL(writeSpeed(int,K3b::Device::SpeedMultiplicator)), this, SIGNAL(writeSpeed(int,K3b::Device::SpeedMultiplicator)) );
179         connect( m_writerJob, SIGNAL(finished(bool)), this, SLOT(slotWriterFinished(bool)) );
180         //    connect( m_writerJob, SIGNAL(newTask(QString)), this, SIGNAL(newTask(QString)) );
181         connect( m_writerJob, SIGNAL(newSubTask(QString)), this, SIGNAL(newSubTask(QString)) );
182         connect( m_writerJob, SIGNAL(debuggingOutput(QString,QString)),
183                  this, SIGNAL(debuggingOutput(QString,QString)) );
184     }
185 
186     m_writerJob->clearArguments();
187     m_writerJob->setWritingMode( K3b::WritingModeRaw );
188     m_writerJob->setClone( true );
189     m_writerJob->setSimulate( m_simulate );
190     m_writerJob->setBurnSpeed( m_speed );
191     m_writerJob->addArgument( m_imagePath );
192 }
193 
194 
cancel()195 void K3b::CloneJob::cancel()
196 {
197     if( m_running ) {
198         m_canceled = true;
199         if( m_readcdReader )
200             m_readcdReader->cancel();
201         if( m_writerJob )
202             m_writerJob->cancel();
203     }
204 }
205 
206 
slotWriterPercent(int p)207 void K3b::CloneJob::slotWriterPercent( int p )
208 {
209     if( m_onlyBurnExistingImage )
210         emit percent( (int)((double)(d->doneCopies)*100.0/(double)(m_copies) + (double)p/(double)(m_copies)) );
211     else
212         emit percent( (int)((double)(1+d->doneCopies)*100.0/(double)(1+m_copies) + (double)p/(double)(1+m_copies)) );
213 }
214 
215 
slotWriterNextTrack(int t,int tt)216 void K3b::CloneJob::slotWriterNextTrack( int t, int tt )
217 {
218     emit newSubTask( i18n("Writing Track %1 of %2",t,tt) );
219 }
220 
221 
slotWriterFinished(bool success)222 void K3b::CloneJob::slotWriterFinished( bool success )
223 {
224     if( m_canceled ) {
225         removeImageFiles();
226         m_running = false;
227         emit canceled();
228         jobFinished(false);
229         return;
230     }
231 
232     if( success ) {
233         d->doneCopies++;
234 
235         emit infoMessage( i18n("Successfully written clone copy %1.",d->doneCopies), MessageInfo );
236 
237         if( d->doneCopies < m_copies ) {
238             K3b::Device::eject( writer() );
239             startWriting();
240         }
241         else {
242             if ( k3bcore->globalSettings()->ejectMedia() ) {
243                 K3b::Device::eject( writer() );
244             }
245 
246             if( m_removeImageFiles )
247                 removeImageFiles();
248             m_running = false;
249             jobFinished(true);
250         }
251     }
252     else {
253         removeImageFiles();
254         m_running = false;
255         jobFinished(false);
256     }
257 }
258 
259 
slotReadingPercent(int p)260 void K3b::CloneJob::slotReadingPercent( int p )
261 {
262     emit percent( m_onlyCreateImage ? p : (int)((double)p/(double)(1+m_copies)) );
263 }
264 
265 
slotReadingFinished(bool success)266 void K3b::CloneJob::slotReadingFinished( bool success )
267 {
268     if( m_canceled ) {
269         removeImageFiles();
270         m_running = false;
271         emit canceled();
272         jobFinished(false);
273         return;
274     }
275 
276     if( success ) {
277         //
278         // Make a quick test if the image is really valid.
279         // Readcd does not seem to have proper exit codes
280         //
281         K3b::CloneTocReader ctr( m_imagePath );
282         if( ctr.isValid() ) {
283             emit infoMessage( i18n("Successfully read disk."), MessageInfo );
284             if( m_onlyCreateImage ) {
285                 m_running = false;
286                 jobFinished(true);
287             }
288             else {
289                 if( writer() == readingDevice() )
290                     K3b::Device::eject( writer() );
291                 startWriting();
292             }
293         }
294         else {
295             emit infoMessage( i18n("Failed to read disk completely in clone mode."), MessageError );
296             removeImageFiles();
297             m_running = false;
298             jobFinished(false);
299         }
300     }
301     else {
302         emit infoMessage( i18n("Error while reading disk."), MessageError );
303         removeImageFiles();
304         m_running = false;
305         jobFinished(false);
306     }
307 }
308 
309 
startWriting()310 void K3b::CloneJob::startWriting()
311 {
312     emit burning( true );
313 
314     // start writing
315     prepareWriter();
316 
317     if( waitForMedium( writer(),
318                       K3b::Device::STATE_EMPTY,
319                       K3b::Device::MEDIA_WRITABLE_CD ) == Device::MEDIA_UNKNOWN ) {
320         removeImageFiles();
321         m_running = false;
322         emit canceled();
323         jobFinished(false);
324         return;
325     }
326 
327     if( m_simulate )
328         emit newTask( i18n("Simulating clone copy") );
329     else
330         emit newTask( i18n("Writing clone copy %1",d->doneCopies+1) );
331 
332     m_writerJob->start();
333 }
334 
335 
removeImageFiles()336 void K3b::CloneJob::removeImageFiles()
337 {
338     if( !m_onlyBurnExistingImage ) {
339         emit infoMessage( i18n("Removing image files."), MessageInfo );
340         if( QFile::exists( m_imagePath ) )
341             QFile::remove( m_imagePath );
342         if( QFile::exists( m_imagePath + ".toc" ) )
343             QFile::remove( m_imagePath + ".toc"  );
344     }
345 }
346 
347 
jobDescription() const348 QString K3b::CloneJob::jobDescription() const
349 {
350     if( m_onlyCreateImage )
351         return i18n("Creating Clone Image");
352     else if( m_onlyBurnExistingImage ) {
353         if( m_simulate )
354             return i18n("Simulating Clone Image");
355         else
356             return i18n("Burning Clone Image");
357     }
358     else if( m_simulate )
359         return i18n("Simulating CD Cloning");
360     else
361         return i18n("Cloning CD");
362 }
363 
364 
jobDetails() const365 QString K3b::CloneJob::jobDetails() const
366 {
367     return i18np("Creating 1 clone copy",
368                  "Creating %1 clone copies",
369                  (m_simulate||m_onlyCreateImage) ? 1 : m_copies );
370 }
371 
372 
jobSource() const373 QString K3b::CloneJob::jobSource() const
374 {
375     if( m_readerDevice )
376         return m_readerDevice->vendor() + ' ' + m_readerDevice->description();
377     else
378         return QString();
379 }
380 
381 
jobTarget() const382 QString K3b::CloneJob::jobTarget() const
383 {
384     if( m_writerDevice )
385         return m_writerDevice->vendor() + ' ' + m_writerDevice->description();
386     else
387         return m_imagePath;
388 }
389 
390 
391