1 /*
2     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
3     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "k3bdataurladdingdialog.h"
8 #include "k3bencodingconverter.h"
9 
10 #include "k3bdatadoc.h"
11 #include "k3bdiritem.h"
12 #include "k3bcore.h"
13 #include "k3bfileitem.h"
14 #include "k3bmultichoicedialog.h"
15 #include "k3bvalidators.h"
16 #include "k3bglobals.h"
17 #include "k3bisooptions.h"
18 #include "k3b.h"
19 #include "k3bapplication.h"
20 #include "k3biso9660.h"
21 #include "k3bdirsizejob.h"
22 #include "k3binteractiondialog.h"
23 #include "k3bthread.h"
24 #include "k3bsignalwaiter.h"
25 #include "k3bexternalbinmanager.h"
26 
27 #include <KConfig>
28 #include <KSqueezedTextLabel>
29 #include <KIconLoader>
30 #include <KLocalizedString>
31 #include <KMessageBox>
32 #include <KStandardGuiItem>
33 #include <kio/listjob.h>
34 
35 #include <QDir>
36 #include <QFileInfo>
37 #include <QList>
38 #include <QUrl>
39 #include <QDialogButtonBox>
40 #include <QGridLayout>
41 #include <QProgressBar>
42 #include <QLabel>
43 #include <QLayout>
44 #include <QInputDialog>
45 
46 #include <unistd.h>
47 
48 
DataUrlAddingDialog(const QList<QUrl> & urls,DirItem * dir,QWidget * parent)49 K3b::DataUrlAddingDialog::DataUrlAddingDialog( const QList<QUrl>& urls, DirItem* dir, QWidget* parent )
50     : DataUrlAddingDialog( dir, parent )
51 {
52     m_urls = urls;
53     for( QList<QUrl>::ConstIterator it = urls.begin(); it != urls.end(); ++it )
54         m_urlQueue.append( qMakePair( K3b::convertToLocalUrl(*it), dir ) );
55 }
56 
57 
DataUrlAddingDialog(const QList<DataItem * > & items,DirItem * dir,bool copy,QWidget * parent)58 K3b::DataUrlAddingDialog::DataUrlAddingDialog( const QList<DataItem*>& items, DirItem* dir, bool copy, QWidget* parent )
59     : DataUrlAddingDialog( dir, parent )
60 {
61     m_infoLabel->setText( i18n("Moving files to project \"%1\"...", dir->getDoc()->URL().fileName()) );
62     m_copyItems = copy;
63 
64     for( QList<K3b::DataItem*>::const_iterator it = items.begin(); it != items.end(); ++it ) {
65         m_items.append( qMakePair( *it, dir ) );
66         ++m_totalFiles;
67         if( (*it)->isDir() ) {
68             m_totalFiles += static_cast<K3b::DirItem*>( *it )->numFiles();
69             m_totalFiles += static_cast<K3b::DirItem*>( *it )->numDirs();
70         }
71     }
72 }
73 
74 
DataUrlAddingDialog(DirItem * dir,QWidget * parent)75 K3b::DataUrlAddingDialog::DataUrlAddingDialog( DirItem* dir, QWidget* parent )
76     : QDialog( parent),
77       m_doc( dir->getDoc() ),
78       m_bExistingItemsReplaceAll(false),
79       m_bExistingItemsIgnoreAll(false),
80       m_bFolderLinksFollowAll(false),
81       m_bFolderLinksAddAll(false),
82       m_iAddHiddenFiles(0),
83       m_iAddSystemFiles(0),
84       m_bCanceled(false),
85       m_copyItems(false),
86       m_totalFiles(0),
87       m_filesHandled(0),
88       m_lastProgress(0)
89 {
90     m_encodingConverter = new K3b::EncodingConverter();
91 
92     setWindowTitle(i18n("Adding files to project '%1'",m_doc->URL().fileName()));
93     setAttribute( Qt::WA_DeleteOnClose );
94     QGridLayout* grid = new QGridLayout( this );
95     grid->setContentsMargins( 0, 0, 0, 0 );
96 
97     m_counterLabel = new QLabel( this );
98     m_infoLabel = new KSqueezedTextLabel( i18n("Adding files to project '%1'..."
99                                                ,m_doc->URL().fileName()), this );
100     m_progressWidget = new QProgressBar( this );
101 
102     QDialogButtonBox* buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel, this );
103     connect( buttonBox, SIGNAL(rejected()), SLOT(reject()) );
104 
105     grid->addWidget( m_counterLabel, 0, 1 );
106     grid->addWidget( m_infoLabel, 0, 0 );
107     grid->addWidget( m_progressWidget, 1, 0, 1, 2 );
108     grid->addWidget( buttonBox, 2, 0, 1, 2 );
109 
110     m_dirSizeJob = new K3b::DirSizeJob( this );
111     connect( m_dirSizeJob, SIGNAL(finished(bool)),
112              this, SLOT(slotDirSizeDone(bool)) );
113 
114     // try to start with a reasonable size
115     resize( (int)( fontMetrics().width( windowTitle() ) * 1.5 ), sizeHint().height() );
116 }
117 
118 
~DataUrlAddingDialog()119 K3b::DataUrlAddingDialog::~DataUrlAddingDialog()
120 {
121     // make sure the dir size job is finished
122     m_dirSizeJob->cancel();
123     K3b::SignalWaiter::waitForJob( m_dirSizeJob );
124 
125     QString message = resultMessage();
126     if( !message.isEmpty() )
127         KMessageBox::detailedSorry( parentWidget(), i18n("Problems while adding files to the project."), message );
128 
129     delete m_encodingConverter;
130 }
131 
132 
addUrls(const QList<QUrl> & urls,K3b::DirItem * dir,QWidget * parent)133 void K3b::DataUrlAddingDialog::addUrls( const QList<QUrl>& urls,
134                                      K3b::DirItem* dir,
135                                      QWidget* parent )
136 {
137     if( !urls.isEmpty() ) {
138         auto *dlg = new DataUrlAddingDialog( urls, dir, parent );
139         dlg->setAttribute(Qt::WA_DeleteOnClose);
140         QMetaObject::invokeMethod( dlg, "slotStartAddUrls", Qt::QueuedConnection );
141     }
142 }
143 
144 
moveItems(const QList<K3b::DataItem * > & items,K3b::DirItem * dir,QWidget * parent)145 void K3b::DataUrlAddingDialog::moveItems( const QList<K3b::DataItem*>& items,
146                                        K3b::DirItem* dir,
147                                        QWidget* parent )
148 {
149     if( !items.isEmpty() ) {
150         auto *dlg = new DataUrlAddingDialog( items, dir, false, parent );
151         dlg->setAttribute(Qt::WA_DeleteOnClose);
152         QMetaObject::invokeMethod( dlg, "slotStartCopyMoveItems", Qt::QueuedConnection );
153     }
154 }
155 
156 
copyItems(const QList<K3b::DataItem * > & items,K3b::DirItem * dir,QWidget * parent)157 void K3b::DataUrlAddingDialog::copyItems( const QList<K3b::DataItem*>& items,
158                                        K3b::DirItem* dir,
159                                        QWidget* parent )
160 {
161     if( !items.isEmpty() ) {
162         auto *dlg = new DataUrlAddingDialog( items, dir, true, parent );
163         dlg->setAttribute(Qt::WA_DeleteOnClose);
164         QMetaObject::invokeMethod( dlg, "slotStartCopyMoveItems", Qt::QueuedConnection );
165     }
166 }
167 
168 
slotStartAddUrls()169 void K3b::DataUrlAddingDialog::slotStartAddUrls()
170 {
171     //
172     // A common mistake by beginners is to try to burn an iso image
173     // with a data project. Let's warn them
174     //
175     if( m_urls.count() == 1 ) {
176         K3b::Iso9660 isoF( m_urls.first().toLocalFile() );
177         if( isoF.open() ) {
178             if( KMessageBox::warningYesNo( parentWidget(),
179                                            i18n("<p>The file you are about to add to the project is an ISO 9660 image. As such "
180                                                 "it can be burned to a medium directly since it already contains a file "
181                                                 "system.<br>"
182                                                 "Are you sure you want to add this file to the project?"),
183                                            i18n("Adding image file to project"),
184                                            KGuiItem(i18n("Add the file to the project"),"list-add"),
185                                            KGuiItem(i18n("Burn the image directly"),"tools-media-optical-burn") ) == KMessageBox::No ) {
186                 k3bappcore->k3bMainWindow()->slotWriteImage( m_urls.first() );
187                 reject();
188                 return;
189             }
190         }
191     }
192 
193     slotAddUrls();
194     if( !m_urlQueue.isEmpty() ) {
195         m_dirSizeJob->setUrls( m_urls );
196         m_dirSizeJob->setFollowSymlinks( m_doc->isoOptions().followSymbolicLinks() );
197         m_dirSizeJob->start();
198         exec();
199     }
200 }
201 
202 
slotStartCopyMoveItems()203 void K3b::DataUrlAddingDialog::slotStartCopyMoveItems()
204 {
205     slotCopyMoveItems();
206     if( !m_items.isEmpty() ) {
207         m_progressWidget->setMaximum( m_totalFiles );
208         exec();
209     }
210 }
211 
212 
slotAddUrls()213 void K3b::DataUrlAddingDialog::slotAddUrls()
214 {
215     if( m_bCanceled )
216         return;
217 
218     // add next url
219     QUrl url = m_urlQueue.first().first;
220     K3b::DirItem* dir = m_urlQueue.first().second;
221     m_urlQueue.erase( m_urlQueue.begin() );
222     //
223     // HINT:
224     // we only use QFileInfo::absoluteFilePath() and QFileInfo::isHidden()
225     // both do not cause QFileInfo to stat, thus no speed improvement
226     // can come from removing QFileInfo usage here.
227     //
228     QFileInfo info(url.toLocalFile());
229     QString absoluteFilePath( info.absoluteFilePath() );
230     QString resolved( absoluteFilePath );
231 
232     bool valid = true;
233     k3b_struct_stat statBuf, resolvedStatBuf;
234     bool isSymLink = false;
235     bool isDir = false;
236     bool isFile = false;
237 
238     ++m_filesHandled;
239 
240     m_infoLabel->setText( url.toLocalFile() );
241     if( m_totalFiles == 0 )
242         m_counterLabel->setText( QString("(%1)").arg(m_filesHandled) );
243     else
244         m_counterLabel->setText( QString("(%1/%2)").arg(m_filesHandled).arg(m_totalFiles) );
245 
246     //
247     // 1. Check if we want and can add the url
248     //
249 
250     if( !url.isLocalFile() ) {
251         valid = false;
252         m_nonLocalFiles.append( url.toLocalFile() );
253     }
254 
255     else if( k3b_lstat( QFile::encodeName(absoluteFilePath), &statBuf ) != 0 ) {
256         valid = false;
257         m_notFoundFiles.append( url.toLocalFile() );
258     }
259 
260     else if( !m_encodingConverter->encodedLocally( QFile::encodeName( url.toLocalFile() ) ) ) {
261         valid = false;
262         m_invalidFilenameEncodingFiles.append( url.toLocalFile() );
263     }
264 
265     else {
266         isSymLink = S_ISLNK(statBuf.st_mode);
267         isFile = S_ISREG(statBuf.st_mode);
268         isDir = S_ISDIR(statBuf.st_mode);
269 
270         // symlinks are always readable and can always be added to a project
271         // but we need to know if the symlink points to a directory
272         if( isSymLink ) {
273             resolved = K3b::resolveLink( absoluteFilePath );
274             k3b_stat( QFile::encodeName(resolved), &resolvedStatBuf );
275             isDir = S_ISDIR(resolvedStatBuf.st_mode);
276         }
277 
278         else {
279             if( ::access( QFile::encodeName( absoluteFilePath ), R_OK ) != 0 ) {
280                 valid = false;
281                 m_unreadableFiles.append( url.toLocalFile() );
282             }
283             else if( isFile && (unsigned long long)statBuf.st_size >= 0xFFFFFFFFULL ) {
284                 const K3b::ExternalBin *mkisofsBin = k3bcore->externalBinManager()->binObject( "mkisofs" );
285                 if ( !mkisofsBin || !mkisofsBin->hasFeature( "no-4gb-limit" ) ) {
286                     valid = false;
287                     m_tooBigFiles.append( url.toLocalFile() );
288                 }
289             }
290         }
291 
292         // FIXME: if we do not add hidden dirs the progress gets messed up!
293 
294         //
295         // check for hidden and system files
296         //
297         if( valid ) {
298             if( info.isHidden() && !addHiddenFiles() )
299                 valid = false;
300             if( S_ISCHR(statBuf.st_mode) ||
301                 S_ISBLK(statBuf.st_mode) ||
302                 S_ISFIFO(statBuf.st_mode) ||
303                 S_ISSOCK(statBuf.st_mode) )
304                 if( !addSystemFiles() )
305                     valid = false;
306             if( isSymLink )
307                 if( S_ISCHR(resolvedStatBuf.st_mode) ||
308                     S_ISBLK(resolvedStatBuf.st_mode) ||
309                     S_ISFIFO(resolvedStatBuf.st_mode) ||
310                     S_ISSOCK(resolvedStatBuf.st_mode) )
311                     if( !addSystemFiles() )
312                         valid = false;
313         }
314     }
315 
316 
317     //
318     // 2. Handle the url
319     //
320 
321     QString newName = url.fileName();
322 
323     // filenames cannot end in backslashes (mkisofs problem. See comments in k3bisoimager.cpp (escapeGraftPoint()))
324     bool bsAtEnd = false;
325     while (!newName.isEmpty() && newName[newName.length() - 1] == '\\') {
326         newName.truncate( newName.length()-1 );
327         bsAtEnd = true;
328     }
329     if( bsAtEnd )
330         m_mkisofsLimitationRenamedFiles.append( url.toLocalFile() + " -> " + newName );
331 
332     // backup dummy name
333     if( newName.isEmpty() )
334         newName = '1';
335 
336     K3b::DirItem* newDirItem = 0;
337 
338     //
339     // The source is valid. Now check if the project already contains a file with that name
340     // and if so handle it properly
341     //
342     if( valid ) {
343         if( K3b::DataItem* oldItem = dir->find( newName ) ) {
344             //
345             // reuse an existing dir
346             //
347             if( oldItem->isDir() && isDir )
348                 newDirItem = dynamic_cast<K3b::DirItem*>(oldItem);
349 
350             //
351             // we cannot replace files in the old session with dirs and vice versa (I think)
352             // files are handled in K3b::FileItem constructor and dirs handled above
353             //
354             else if( oldItem->isFromOldSession() &&
355                      isDir != oldItem->isDir() ) {
356                 if( !getNewName( newName, dir, newName ) )
357                     valid = false;
358             }
359 
360             else if( m_bExistingItemsIgnoreAll )
361                 valid = false;
362 
363             else if( oldItem->localPath() == resolved ) {
364                 //
365                 // Just ignore if the same file is added again
366                 //
367                 valid = false;
368             }
369 
370             else if( m_bExistingItemsReplaceAll ) {
371                 // if we replace an item from an old session the K3b::FileItem constructor takes care
372                 // of replacing the item
373                 if( !oldItem->isFromOldSession() )
374                     delete oldItem;
375             }
376 
377             //
378             // Let the user choose
379             //
380             else {
381                 switch( K3b::MultiChoiceDialog::choose( i18n("File already exists"),
382                                                       i18n("<p>File <em>%1</em> already exists in "
383                                                            "project folder <em>%2</em>.",
384                                                            newName,
385                                                            QString( '/' + dir->k3bPath()) ),
386                                                       QMessageBox::Warning,
387                                                       this,
388                                                       6,
389                                                       KGuiItem( i18n("Replace"),
390                                                                 QString(),
391                                                                 i18n("Replace the existing file") ),
392                                                       KGuiItem( i18n("Replace All"),
393                                                                 QString(),
394                                                                 i18n("Always replace existing files") ),
395                                                       KGuiItem( i18n("Ignore"),
396                                                                 QString(),
397                                                                 i18n("Keep the existing file") ),
398                                                       KGuiItem( i18n("Ignore All"),
399                                                                 QString(),
400                                                                 i18n("Always keep the existing file") ),
401                                                       KGuiItem( i18n("Rename"),
402                                                                 QString(),
403                                                                 i18n("Rename the new file") ),
404                                                       KStandardGuiItem::cancel() ) ) {
405                 case 2: // replace all
406                     m_bExistingItemsReplaceAll = true;
407                     // fallthrough
408                 case 1: // replace
409                     // if we replace an item from an old session the K3b::FileItem constructor takes care
410                     // of replacing the item
411                     if( !oldItem->isFromOldSession() )
412                         delete oldItem;
413                     break;
414                 case 4: // ignore all
415                     m_bExistingItemsIgnoreAll = true;
416                     // fallthrough
417                 case 3: // ignore
418                     valid = false;
419                     break;
420                 case 5: // rename
421                     if( !getNewName( newName, dir, newName ) )
422                         valid = false;
423                     break;
424                 case 6: // cancel
425                     reject();
426                     return;
427                 }
428             }
429         }
430     }
431 
432 
433     //
434     // One more thing to warn the user about: We cannot follow links to folders since that
435     // would change the doc. So we simply ask the user what to do with a link to a folder
436     //
437     if( valid ) {
438         // let's see if this link starts a loop
439         // that means if it points to some folder above this one
440         // if so we cannot follow it anyway
441         if( isDir && isSymLink && !absoluteFilePath.startsWith( resolved ) ) {
442             bool followLink = dir->getDoc()->isoOptions().followSymbolicLinks() || m_bFolderLinksFollowAll;
443             if( !followLink && !m_bFolderLinksAddAll ) {
444                 switch( K3b::MultiChoiceDialog::choose( i18n("Adding link to folder"),
445                                                       i18n("<p>'%1' is a symbolic link to folder '%2'."
446                                                            "<p>If you intend to make K3b follow symbolic links you should consider letting K3b do this now "
447                                                            "since K3b will not be able to do so afterwards because symbolic links to folders inside a "
448                                                            "K3b project cannot be resolved."
449                                                            "<p><b>If you do not intend to enable the option <em>follow symbolic links</em> you may safely "
450                                                            "ignore this warning and choose to add the link to the project.</b>",
451                                                            absoluteFilePath,
452                                                            resolved ),
453                                                       QMessageBox::Warning,
454                                                       this,
455                                                       5,
456                                                       KGuiItem(i18n("Follow link now")),
457                                                       KGuiItem(i18n("Always follow links")),
458                                                       KGuiItem(i18n("Add link to project")),
459                                                       KGuiItem(i18n("Always add links")),
460                                                       KStandardGuiItem::cancel())  ) {
461                 case 2:
462                     m_bFolderLinksFollowAll = true;
463                     Q_FALLTHROUGH();
464                 case 1:
465                     followLink = true;
466                     break;
467                 case 4:
468                     m_bFolderLinksAddAll = true;
469                     Q_FALLTHROUGH();
470                 case 3:
471                     followLink = false;
472                     break;
473                 case 5:
474                     reject();
475                     return;
476                 }
477             }
478 
479             if( followLink ) {
480                 absoluteFilePath = resolved;
481                 isSymLink = false;
482 
483                 // count the files in the followed dir
484                 if( m_dirSizeJob->active() )
485                     m_dirSizeQueue.append( QUrl::fromLocalFile(absoluteFilePath) );
486                 else {
487                     m_progressWidget->setMaximum( 0 );
488                     m_dirSizeJob->setUrls( QList<QUrl>() << QUrl::fromLocalFile(absoluteFilePath) );
489                     m_dirSizeJob->start();
490                 }
491             }
492         }
493     }
494 
495 
496     //
497     // Project valid also (we overwrite or renamed)
498     // now create the new item
499     //
500     if( valid ) {
501         //
502         // Set the volume id from the first added url
503         // only if the doc was not changed yet
504         //
505         if( m_urls.count() == 1 &&
506             !dir->getDoc()->isModified() &&
507             !dir->getDoc()->isSaved() ) {
508             dir->getDoc()->setVolumeID( K3b::removeFilenameExtension( newName ) );
509         }
510 
511         if( isDir && !isSymLink ) {
512             if( !newDirItem ) { // maybe we reuse an already existing dir
513                 newDirItem = new K3b::DirItem( newName );
514                 newDirItem->setLocalPath( url.toLocalFile() ); // HACK: see k3bdiritem.h
515                 dir->addDataItem( newDirItem );
516             }
517 
518             KIO::ListJob *lj = KIO::listDir(QUrl::fromLocalFile(absoluteFilePath), KIO::HideProgressInfo);
519             KIO::UDSEntryList list;
520             connect(lj, &KIO::ListJob::entries, this, [&](KIO::Job *, const KIO::UDSEntryList &l) { list.append(l); });
521             lj->exec();
522             foreach( const KIO::UDSEntry& entry, list ) {
523                 const QString fileName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
524                 if (fileName != QStringLiteral(".") && fileName != QStringLiteral("..")) {
525                     m_urlQueue.append( qMakePair( QUrl::fromLocalFile(absoluteFilePath + '/' + fileName ), newDirItem ) );
526                 }
527             }
528         }
529         else {
530             m_newItems[ dir ].append( new K3b::FileItem( &statBuf, &resolvedStatBuf, url.toLocalFile(), *dir->getDoc(), newName ) );
531         }
532     }
533 
534     if( m_urlQueue.isEmpty() ) {
535         Q_FOREACH( DirItem* dir, m_newItems.keys() ) {
536             dir->addDataItems( m_newItems[ dir ] );
537         }
538         m_dirSizeJob->cancel();
539         m_progressWidget->setMaximum( 100 );
540         accept();
541     }
542     else {
543         updateProgress();
544         QMetaObject::invokeMethod( this, "slotAddUrls", Qt::QueuedConnection );
545     }
546 }
547 
548 
slotCopyMoveItems()549 void K3b::DataUrlAddingDialog::slotCopyMoveItems()
550 {
551     if( m_bCanceled )
552         return;
553 
554     //
555     // Pop first item from the item list
556     //
557     K3b::DataItem* item = m_items.first().first;
558     K3b::DirItem* dir = m_items.first().second;
559     m_items.erase( m_items.begin() );
560 
561     ++m_filesHandled;
562     m_infoLabel->setText( item->k3bPath() );
563     if( m_totalFiles == 0 )
564         m_counterLabel->setText( QString("(%1)").arg(m_filesHandled) );
565     else
566         m_counterLabel->setText( QString("(%1/%2)").arg(m_filesHandled).arg(m_totalFiles) );
567 
568 
569     if( dir == item->parent() ) {
570         qDebug() << "(K3b::DataUrlAddingDialog) trying to move an item into its own parent dir.";
571     }
572     else if( dir == item ) {
573         qDebug() << "(K3b::DataUrlAddingDialog) trying to move an item into itselft.";
574     }
575     else {
576         //
577         // Let's see if an item with that name already exists
578         //
579         if( K3b::DataItem* oldItem = dir->find( item->k3bName() ) ) {
580             //
581             // reuse an existing dir: move all child items into the old dir
582             //
583             if( oldItem->isDir() && item->isDir() ) {
584                 QList<K3b::DataItem*> const& cl = dynamic_cast<K3b::DirItem*>( item )->children();
585                 for( QList<K3b::DataItem*>::const_iterator it = cl.constBegin();
586                      it != cl.constEnd(); ++it )
587                     m_items.append( qMakePair( *it, dynamic_cast<K3b::DirItem*>( oldItem ) ) );
588 
589                 // FIXME: we need to remove the old dir item
590             }
591 
592             //
593             // we cannot replace files in the old session with dirs and vice versa (I think)
594             // files are handled in K3b::FileItem constructor and dirs handled above
595             //
596             else if( oldItem->isFromOldSession() &&
597                      item->isDir() != oldItem->isDir() ) {
598                 QString newName;
599                 if( getNewName( newName, dir, newName ) ) {
600                     if( m_copyItems )
601                         item = item->copy();
602                     item->setK3bName( newName );
603                     dir->addDataItem( item );
604                 }
605             }
606 
607             else if( m_bExistingItemsReplaceAll ) {
608                 //
609                 // if we replace an item from an old session K3b::DirItem::addDataItem takes care
610                 // of replacing the item
611                 //
612                 if( !oldItem->isFromOldSession() )
613                     delete oldItem;
614                 if( m_copyItems )
615                     item = item->copy();
616                 dir->addDataItem( item );
617             }
618 
619             else if( !m_bExistingItemsIgnoreAll ) {
620                 switch( K3b::MultiChoiceDialog::choose( i18n("File already exists"),
621                                                       i18n("<p>File <em>%1</em> already exists in "
622                                                            "project folder <em>%2</em>.",
623                                                       item->k3bName(),
624                                                       '/' + dir->k3bPath() ),
625                                                       QMessageBox::Warning,
626                                                       this,
627                                                       6,
628                                                       KGuiItem( i18n("Replace"),
629                                                                 QString(),
630                                                                 i18n("Replace the existing file") ),
631                                                       KGuiItem( i18n("Replace All"),
632                                                                 QString(),
633                                                                 i18n("Always replace existing files") ),
634                                                       KGuiItem( i18n("Ignore"),
635                                                                 QString(),
636                                                                 i18n("Keep the existing file") ),
637                                                       KGuiItem( i18n("Ignore All"),
638                                                                 QString(),
639                                                                 i18n("Always keep the existing file") ),
640                                                       KGuiItem( i18n("Rename"),
641                                                                 QString(),
642                                                                 i18n("Rename the new file") ),
643                                                       KStandardGuiItem::cancel() ) ) {
644                 case 2: // replace all
645                     m_bExistingItemsReplaceAll = true;
646                     // fallthrough
647                 case 1: // replace
648                     //
649                     // if we replace an item from an old session K3b::DirItem::addDataItem takes care
650                     // of replacing the item
651                     //
652                     if( !oldItem->isFromOldSession() )
653                         delete oldItem;
654                     if( m_copyItems )
655                         item = item->copy();
656                     dir->addDataItem( item );
657                     break;
658                 case 4: // ignore all
659                     m_bExistingItemsIgnoreAll = true;
660                     // fallthrough
661                 case 3: // ignore
662                     // do nothing
663                     break;
664                 case 5: {// rename
665                     QString newName;
666                     if( getNewName( newName, dir, newName ) ) {
667                         if( m_copyItems )
668                             item = item->copy();
669                         item->setK3bName( newName );
670                         dir->addDataItem( item );
671                     }
672                     break;
673                 }
674                 case 6: // cancel
675                     reject();
676                     return;
677                 }
678             }
679         }
680 
681         //
682         // No old item with the same name
683         //
684         else {
685             if( m_copyItems )
686                 item = item->copy();
687             dir->addDataItem( item );
688         }
689     }
690 
691     if( m_items.isEmpty() ) {
692         m_dirSizeJob->cancel();
693         accept();
694     }
695     else {
696         updateProgress();
697         QMetaObject::invokeMethod( this, "slotCopyMoveItems", Qt::QueuedConnection );
698     }
699 }
700 
701 
reject()702 void K3b::DataUrlAddingDialog::reject()
703 {
704     m_bCanceled = true;
705     m_dirSizeJob->cancel();
706     QDialog::reject();
707 }
708 
709 
slotDirSizeDone(bool success)710 void K3b::DataUrlAddingDialog::slotDirSizeDone( bool success )
711 {
712     if( success ) {
713         m_totalFiles += m_dirSizeJob->totalFiles() + m_dirSizeJob->totalDirs();
714         if( m_dirSizeQueue.isEmpty() ) {
715             m_progressWidget->setValue( 100 );
716             updateProgress();
717         }
718         else {
719             m_dirSizeJob->setUrls( QList<QUrl>() << m_dirSizeQueue.back() );
720             m_dirSizeQueue.pop_back();
721             m_dirSizeJob->start();
722         }
723     }
724 }
725 
726 
updateProgress()727 void K3b::DataUrlAddingDialog::updateProgress()
728 {
729     if( m_totalFiles > 0 ) {
730         unsigned int p = 100*m_filesHandled/m_totalFiles;
731         if( p > m_lastProgress ) {
732             m_lastProgress = p;
733             m_progressWidget->setValue( p );
734         }
735     }
736     else {
737         // make sure the progress bar shows something
738         m_progressWidget->setValue( m_filesHandled );
739     }
740 }
741 
742 
getNewName(const QString & oldName,K3b::DirItem * dir,QString & newName)743 bool K3b::DataUrlAddingDialog::getNewName( const QString& oldName, K3b::DirItem* dir, QString& newName )
744 {
745     bool ok = true;
746     newName = oldName;
747     QValidator* validator = K3b::Validators::iso9660Validator( false, this );
748     int pos;
749     do {
750         newName = QInputDialog::getText( this,
751                                          i18n("Enter New Filename"),
752                                          i18n("A file with that name already exists. Please enter a new name:"),
753                                          QLineEdit::Normal,
754                                          newName, &ok );
755 
756     } while( ok && validator->validate( newName, pos ) == QValidator::Acceptable && dir->find( newName ) );
757 
758     delete validator;
759 
760     return ok;
761 }
762 
763 
addHiddenFiles()764 bool K3b::DataUrlAddingDialog::addHiddenFiles()
765 {
766     if( m_iAddHiddenFiles == 0 ) {
767         // FIXME: the isVisible() stuff makes the static addUrls method not return (same below)
768         if( KMessageBox::questionYesNo( /*isVisible() ? */this/* : parentWidget()*/,
769                                         i18n("Do you also want to add hidden files?"),
770                                         i18n("Hidden Files"), KGuiItem(i18n("Add")), KGuiItem(i18n("Do Not Add")) ) == KMessageBox::Yes )
771             m_iAddHiddenFiles = 1;
772         else
773             m_iAddHiddenFiles = -1;
774     }
775 
776     return ( m_iAddHiddenFiles == 1 );
777 }
778 
779 
addSystemFiles()780 bool K3b::DataUrlAddingDialog::addSystemFiles()
781 {
782     if( m_iAddSystemFiles == 0 ) {
783         if( KMessageBox::questionYesNo( /*isVisible() ? */this/* : parentWidget()*/,
784                                         i18n("Do you also want to add system files "
785                                              "(FIFOs, sockets, device files, and broken symlinks)?"),
786                                         i18n("System Files"), KGuiItem(i18n("Add")), KGuiItem(i18n("Do Not Add")) ) == KMessageBox::Yes )
787             m_iAddSystemFiles = 1;
788         else
789             m_iAddSystemFiles = -1;
790     }
791 
792     return ( m_iAddSystemFiles == 1 );
793 }
794 
795 
resultMessage() const796 QString K3b::DataUrlAddingDialog::resultMessage() const
797 {
798     QString message;
799     if( !m_unreadableFiles.isEmpty() )
800         message += QString("<p><b>%1:</b><br>%2")
801                    .arg( i18n("Insufficient permissions to read the following files") )
802                    .arg( m_unreadableFiles.join( "<br>" ) );
803     if( !m_notFoundFiles.isEmpty() )
804         message += QString("<p><b>%1:</b><br>%2")
805                    .arg( i18n("Unable to find the following files") )
806                    .arg( m_notFoundFiles.join( "<br>" ) );
807     if( !m_nonLocalFiles.isEmpty() )
808         message += QString("<p><b>%1:</b><br>%2")
809                    .arg( i18n("No non-local files supported") )
810                    .arg( m_unreadableFiles.join( "<br>" ) );
811     if( !m_tooBigFiles.isEmpty() )
812         message += QString("<p><b>%1:</b><br>%2")
813                    .arg( i18n("To burn files bigger than %1 please use %2",KIO::convertSize(0xFFFFFFFF),
814                               QString("mkisofs >= 2.01.01a33 / genisoimage >= 1.1.4") ) )
815                    .arg( m_tooBigFiles.join( "<br>" ) );
816     if( !m_mkisofsLimitationRenamedFiles.isEmpty() )
817         message += QString("<p><b>%1:</b><br>%2")
818                    .arg( i18n("Some filenames had to be modified due to limitations in mkisofs") )
819                    .arg( m_mkisofsLimitationRenamedFiles.join( "<br>" ) );
820     if( !m_invalidFilenameEncodingFiles.isEmpty() )
821         message += QString("<p><b>%1:</b><br>%2")
822                    .arg( i18n("The following filenames have an invalid encoding. You may fix this "
823                               "with the convmv tool") )
824                    .arg( m_invalidFilenameEncodingFiles.join( "<br>" ) );
825 
826     return message;
827 }
828 
829 
830