1 
2 #include "filelist.h"
3 // // #include "filelistitem.h"
4 #include "config.h"
5 #include "logger.h"
6 #include "optionseditor.h"
7 #include "optionslayer.h"
8 #include "core/conversionoptions.h"
9 #include "outputdirectory.h"
10 #include "codecproblems.h"
11 
12 #include <KApplication>
13 #include <KIcon>
14 #include <KAction>
15 // #include <kactioncollection.h>
16 #include <KMessageBox>
17 #include <KStandardDirs>
18 // #include <KDiskFreeSpaceInfo>
19 #include <kmountpoint.h>
20 // #include <KIO/Job>
21 #include <solid/device.h>
22 #include <solid/block.h>
23 #include <solid/opticaldrive.h>
24 
25 #include <QLayout>
26 #include <QGridLayout>
27 #include <QStringList>
28 #include <QMenu>
29 #include <QFile>
30 #include <QResizeEvent>
31 #include <QDir>
32 #include <QProgressBar>
33 
34 
FileList(Logger * _logger,Config * _config,QWidget * parent)35 FileList::FileList( Logger *_logger, Config *_config, QWidget *parent )
36     : QTreeWidget( parent ),
37     logger( _logger ),
38     config( _config )
39 {
40     queue = false;
41     optionsEditor = 0;
42     tagEngine = config->tagEngine();
43 
44     setAcceptDrops( true );
45     setDragEnabled( false );
46 
47     setItemDelegate( new FileListItemDelegate(this) );
48 
49     setColumnCount( 4 );
50     QStringList labels;
51     labels.append( i18n("State") );
52     labels.append( i18n("Input") );
53     labels.append( i18n("Output") );
54     labels.append( i18n("Quality") );
55     setHeaderLabels( labels );
56 //     header()->setClickEnabled( false );
57 
58     setSelectionBehavior( QAbstractItemView::SelectRows );
59     setSelectionMode( QAbstractItemView::ExtendedSelection );
60     setSortingEnabled( false );
61 
62     setRootIsDecorated( false );
63     setDragDropMode( QAbstractItemView::InternalMove );
64 
65     QGridLayout *grid = new QGridLayout( this );
66     grid->setRowStretch( 0, 1 );
67     grid->setRowStretch( 2, 1 );
68     grid->setColumnStretch( 0, 1 );
69     grid->setColumnStretch( 2, 1 );
70     pScanStatus = new QProgressBar( this );
71     pScanStatus->setMinimumHeight( pScanStatus->height() );
72     pScanStatus->setFormat( "%v / %m" );
73     pScanStatus->hide();
74     grid->addWidget( pScanStatus, 1, 1 );
75     grid->setColumnStretch( 1, 2 );
76 
77     // we haven't got access to the action collection of soundKonverter, so let's create a new one
78 //     actionCollection = new KActionCollection( this );
79 
80     editAction = new KAction( KIcon("view-list-text"), i18n("Edit options..."), this );
81     connect( editAction, SIGNAL(triggered()), this, SLOT(showOptionsEditorDialog()) );
82     startAction = new KAction( KIcon("system-run"), i18n("Start conversion"), this );
83     connect( startAction, SIGNAL(triggered()), this, SLOT(convertSelectedItems()) );
84     stopAction = new KAction( KIcon("process-stop"), i18n("Stop conversion"), this );
85     connect( stopAction, SIGNAL(triggered()), this, SLOT(killSelectedItems()) );
86     removeAction = new KAction( KIcon("edit-delete"), i18n("Remove"), this );
87     removeAction->setShortcut( QKeySequence::Delete );
88     connect( removeAction, SIGNAL(triggered()), this, SLOT(removeSelectedItems()) );
89     addAction( removeAction );
90 //     KAction *removeActionGlobal = new KAction( KIcon("edit-delete"), i18n("Remove"), this );
91 //     removeActionGlobal->setShortcut( QKeySequence::Delete );
92 //     connect( removeActionGlobal, SIGNAL(triggered()), this, SLOT(removeSelectedItems()) );
93 //     addAction( removeActionGlobal );
94 //     paste = new KAction( i18n("Paste"), "editpaste", 0, this, 0, actionCollection, "paste" );  // TODO paste
95 
96     contextMenu = new QMenu( this );
97 
98     setContextMenuPolicy( Qt::CustomContextMenu );
99     connect( this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&)) );
100 
101     connect( this, SIGNAL(itemSelectionChanged()), this, SLOT(itemsSelected()) );
102 }
103 
~FileList()104 FileList::~FileList()
105 {
106     // NOTE no cleanup needed since it all gets cleaned up in other classes
107 
108     if( !KApplication::kApplication()->sessionSaving() )
109     {
110         QFile listFile( KStandardDirs::locateLocal("data","soundkonverter/filelist_autosave.xml") );
111         listFile.remove();
112     }
113 }
114 
dragEnterEvent(QDragEnterEvent * event)115 void FileList::dragEnterEvent( QDragEnterEvent *event )
116 {
117     if( event->mimeData()->hasFormat("text/uri-list") )
118         event->acceptProposedAction();
119 }
120 
dropEvent(QDropEvent * event)121 void FileList::dropEvent( QDropEvent *event )
122 {
123     QList<QUrl> q_urls = event->mimeData()->urls();
124     KUrl::List k_urls;
125     QStringList errorList;
126     //    codec    @0 files @1 solutions
127     QMap< QString, QList<QStringList> > problems;
128     QString fileName;
129 
130     const bool canDecodeAac = config->pluginLoader()->canDecode( "m4a/aac" );
131     const bool canDecodeAlac = config->pluginLoader()->canDecode( "m4a/alac" );
132     const bool checkM4a = ( !canDecodeAac || !canDecodeAlac ) && canDecodeAac != canDecodeAlac;
133 
134     for( int i=0; i<q_urls.size(); i++ )
135     {
136         QString mimeType;
137         QString codecName = config->pluginLoader()->getCodecFromFile( q_urls.at(i), &mimeType, checkM4a );
138 
139         if( mimeType == "inode/directory" || config->pluginLoader()->canDecode(codecName,&errorList) )
140         {
141             k_urls += q_urls.at(i);
142         }
143         else
144         {
145             if( codecName.isEmpty() && !mimeType.startsWith("audio") && !mimeType.startsWith("video") && !mimeType.startsWith("application") )
146                 continue;
147 
148             if( mimeType == "application/x-ole-storage" || // Thumbs.db
149                 mimeType == "application/x-wine-extension-ini" ||
150                 mimeType == "application/x-cue" ||
151                 mimeType == "application/x-k3b" ||
152                 mimeType == "application/pdf" ||
153                 mimeType == "application/x-trash" ||
154                 mimeType.startsWith("application/vnd.oasis.opendocument") ||
155                 mimeType.startsWith("application/vnd.openxmlformats-officedocument") ||
156                 mimeType.startsWith("application/vnd.sun.xml")
157             )
158                 continue;
159 
160             fileName = KUrl(q_urls.at(i)).pathOrUrl();
161 
162             if( codecName.isEmpty() )
163                 codecName = mimeType;
164             if( codecName.isEmpty() )
165                 codecName = fileName.right(fileName.length()-fileName.lastIndexOf(".")-1);
166 
167             if( problems.value(codecName).count() < 2 )
168             {
169                 problems[codecName] += QStringList();
170                 problems[codecName] += QStringList();
171             }
172             problems[codecName][0] += fileName;
173             if( !errorList.isEmpty() )
174             {
175                 problems[codecName][1] += errorList;
176             }
177             else
178             {
179                 problems[codecName][1] += i18n("This file type is unknown to soundKonverter.\nMaybe you need to install an additional soundKonverter plugin.\nYou should have a look at your distribution's package manager for this.");
180             }
181         }
182     }
183 
184     QList<CodecProblems::Problem> problemList;
185     for( int i=0; i<problems.count(); i++ )
186     {
187         CodecProblems::Problem problem;
188         problem.codecName = problems.keys().at(i);
189         if( problem.codecName != "wav" )
190         {
191             problems[problem.codecName][1].removeDuplicates();
192             problem.solutions = problems.value(problem.codecName).at(1);
193             if( problems.value(problem.codecName).at(0).count() <= 3 )
194             {
195                 problem.affectedFiles = problems.value(problem.codecName).at(0);
196             }
197             else
198             {
199                 problem.affectedFiles += problems.value(problem.codecName).at(0).at(0);
200                 problem.affectedFiles += problems.value(problem.codecName).at(0).at(1);
201                 problem.affectedFiles += i18n("... and %1 more files",problems.value(problem.codecName).at(0).count()-3);
202             }
203             problemList += problem;
204         }
205     }
206 
207     if( problemList.count() > 0 )
208     {
209         CodecProblems *problemsDialog = new CodecProblems( CodecProblems::Decode, problemList, this );
210         problemsDialog->exec();
211     }
212 
213     if( k_urls.count() > 0 )
214     {
215         const ConversionOptions *conversionOptions = config->data.profiles.value("soundkonverter_last_used");
216         if( conversionOptions )
217             optionsLayer->setCurrentConversionOptions( conversionOptions );
218         optionsLayer->addUrls( k_urls );
219         optionsLayer->fadeIn();
220     }
221 
222     event->acceptProposedAction();
223 }
224 
resizeEvent(QResizeEvent * event)225 void FileList::resizeEvent( QResizeEvent *event )
226 {
227     if( event->size().width() < 500 )
228         return;
229 
230     setColumnWidth( Column_State, 150 );
231     setColumnWidth( Column_Input, (event->size().width()-270)/2 );
232     setColumnWidth( Column_Output, (event->size().width()-270)/2 );
233     setColumnWidth( Column_Quality, 120 );
234 }
235 
countDir(const QString & directory,bool recursive,int count)236 int FileList::countDir( const QString& directory, bool recursive, int count )
237 {
238     QDir dir( directory );
239     dir.setFilter( QDir::Files | QDir::NoDotAndDotDot | QDir::Readable );
240     dir.setSorting( QDir::Unsorted );
241 
242     count += (int)dir.count();
243 
244     if( recursive )
245     {
246         dir.setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable );
247 
248         const QStringList list = dir.entryList();
249 
250         foreach( const QString& fileName, list )
251         {
252             count = countDir( directory + "/" + fileName, recursive, count );
253         }
254     }
255 
256     if( tScanStatus.elapsed() > ConfigUpdateDelay * 10 )
257     {
258         pScanStatus->setMaximum( count );
259         kapp->processEvents();
260         tScanStatus.start();
261     }
262 
263     return count;
264 }
265 
listDir(const QString & directory,const QStringList & filter,bool recursive,int conversionOptionsId,int count)266 int FileList::listDir( const QString& directory, const QStringList& filter, bool recursive, int conversionOptionsId, int count )
267 {
268     QString codecName;
269 
270     QDir dir( directory );
271     dir.setFilter( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable );
272     dir.setSorting( QDir::LocaleAware );
273 
274     const QStringList list = dir.entryList();
275 
276     const bool containsAac = filter.contains("m4a/aac");
277     const bool containsAlac = filter.contains("m4a/alac");
278     const bool checkM4a = ( containsAac || containsAlac ) && containsAac != containsAlac;
279 
280     foreach( const QString& fileName, list )
281     {
282         QFileInfo fileInfo( directory + "/" + fileName );
283 
284         const bool isDir = fileInfo.isDir(); // NOTE checking for isFile might not work with all file names
285         if( isDir && recursive )
286         {
287             count = listDir( directory + "/" + fileName, filter, recursive, conversionOptionsId, count );
288         }
289         else if( !isDir )
290         {
291             count++;
292 
293             codecName = config->pluginLoader()->getCodecFromFile( directory + "/" + fileName, 0, checkM4a );
294 
295             if( filter.contains(codecName) )
296             {
297                 QList<QUrl> urls;
298                 urls.append("file://" + directory + "/" + fileName);
299                 addFiles( urls, 0, "", codecName, conversionOptionsId );
300             }
301 
302             if( tScanStatus.elapsed() > ConfigUpdateDelay * 10 )
303             {
304                 pScanStatus->setValue( count );
305                 tScanStatus.start();
306             }
307         }
308     }
309 
310     return count;
311 }
312 
addFiles(const QList<QUrl> & fileList,ConversionOptions * conversionOptions,const QString & command,const QString & _codecName,int conversionOptionsId)313 void FileList::addFiles( const QList<QUrl>& fileList, ConversionOptions *conversionOptions, const QString& command, const QString& _codecName, int conversionOptionsId )
314 {
315     QString codecName;
316     QString filePathName;
317     QString device;
318 
319     int lastConversionOptionsId = -1;
320 
321     bool optionsLayerHidden = false; // shouldn't be necessary
322 
323     if( !conversionOptions && conversionOptionsId == -1 )
324     {
325         logger->log( 1000, "@addFiles: No conversion options given" );
326         return;
327     }
328 
329     int batchNumber = 0;
330     foreach( const QUrl& fileName, fileList )
331     {
332         if( !_codecName.isEmpty() )
333         {
334             codecName = _codecName;
335         }
336         else
337         {
338             QFileInfo fileInfo( fileName.toLocalFile() );
339             if( fileInfo.isDir() )
340             {
341                 if( !optionsLayerHidden && QObject::sender() == optionsLayer )
342                 {
343                     optionsLayerHidden = true;
344                     optionsLayer->hide();
345                     kapp->processEvents();
346                 }
347 
348                 //             debug
349                 //             logger->log( 1000, "@addFiles: adding dir: " + fileName.toLocalFile() );
350 
351                 addDir( fileName, true, config->pluginLoader()->formatList(PluginLoader::Decode,PluginLoader::CompressionType(PluginLoader::InferiorQuality|PluginLoader::Lossy|PluginLoader::Lossless|PluginLoader::Hybrid)), conversionOptions );
352                 continue;
353             }
354             else
355             {
356                 const bool canDecodeAac = config->pluginLoader()->canDecode( "m4a/aac" );
357                 const bool canDecodeAlac = config->pluginLoader()->canDecode( "m4a/alac" );
358                 const bool checkM4a = ( !canDecodeAac || !canDecodeAlac ) && canDecodeAac != canDecodeAlac;
359                 codecName = config->pluginLoader()->getCodecFromFile( fileName, 0, checkM4a );
360 
361                 if( !config->pluginLoader()->canDecode(codecName) )
362                     continue;
363             }
364         }
365 
366 //         logger->log( 1000, "adding file: " + fileName.toLocalFile() + ", codec: " + codecName );
367 
368         FileListItem * const newItem = new FileListItem( this );
369         if( conversionOptionsId == -1 )
370         {
371             if( batchNumber == 0 )
372             {
373                 newItem->conversionOptionsId = config->conversionOptionsManager()->addConversionOptions( conversionOptions );
374             }
375             else
376             {
377                 newItem->conversionOptionsId = config->conversionOptionsManager()->increaseReferences( lastConversionOptionsId );
378             }
379         }
380         else
381         {
382             newItem->conversionOptionsId = config->conversionOptionsManager()->increaseReferences( conversionOptionsId );
383         }
384 
385         lastConversionOptionsId = newItem->conversionOptionsId;
386         newItem->codecName = codecName;
387         newItem->track = -1;
388         newItem->url = fileName;
389         newItem->local = ( newItem->url.isLocalFile() || newItem->url.protocol() == "file" );
390         newItem->tags = tagEngine->readTags( newItem->url );
391         if( !newItem->tags && newItem->codecName == "wav" && newItem->local )
392         {
393             QFile file( newItem->url.toLocalFile() );
394             newItem->length = file.size() / 176400; // assuming it's a 44100 Hz, 16 bit wave file
395         }
396         else
397         {
398             newItem->length = ( newItem->tags && newItem->tags->length > 0 ) ? newItem->tags->length : 200.0f;
399         }
400         newItem->notifyCommand = command;
401 
402         addTopLevelItem( newItem );
403         updateItem( newItem );
404         emit timeChanged( newItem->length );
405 
406         batchNumber++;
407 
408         if( batchNumber % 50 == 0 )
409             kapp->processEvents();
410     }
411 
412     if( !pScanStatus->isVisible() )
413     {
414         emit fileCountChanged( topLevelItemCount() );
415 
416         if( queue )
417             convertNextItem();
418     }
419 }
420 
addDir(const QUrl & directory,bool recursive,const QStringList & codecList,ConversionOptions * conversionOptions)421 void FileList::addDir( const QUrl& directory, bool recursive, const QStringList& codecList, ConversionOptions *conversionOptions )
422 {
423     if( !conversionOptions )
424     {
425         logger->log( 1000, "@addDir: No conversion options given" );
426         return;
427     }
428 
429     const int conversionOptionsId = config->conversionOptionsManager()->addConversionOptions( conversionOptions );
430 
431     pScanStatus->setValue( 0 );
432     pScanStatus->setMaximum( 0 );
433     pScanStatus->show(); // show the status while scanning the directories
434     tScanStatus.start();
435 
436     const int count = countDir( directory.toLocalFile(), recursive );
437 
438     pScanStatus->setMaximum( count );
439     kapp->processEvents();
440 
441     listDir( directory.toLocalFile(), codecList, recursive, conversionOptionsId );
442 
443     pScanStatus->hide(); // hide the status bar, when the scan is done
444 
445     emit fileCountChanged( topLevelItemCount() );
446 
447     if( queue )
448         convertNextItem();
449 }
450 
addTracks(const QString & device,QList<int> trackList,int tracks,QList<TagData * > tagList,ConversionOptions * conversionOptions,const QString & notifyCommand)451 void FileList::addTracks( const QString& device, QList<int> trackList, int tracks, QList<TagData*> tagList, ConversionOptions *conversionOptions, const QString& notifyCommand )
452 {
453     FileListItem *lastListItem = 0;
454 
455     if( !conversionOptions )
456     {
457         logger->log( 1000, "@addTracks: No conversion options given" );
458         return;
459     }
460 
461     for( int i=0; i<trackList.count(); i++ )
462     {
463         FileListItem *newItem = new FileListItem( this );
464         if( i == 0 )
465         {
466             newItem->conversionOptionsId = config->conversionOptionsManager()->addConversionOptions( conversionOptions );
467         }
468         else
469         {
470             newItem->conversionOptionsId = config->conversionOptionsManager()->increaseReferences( lastListItem->conversionOptionsId );
471         }
472         lastListItem = newItem;
473         newItem->codecName = "audio cd";
474         newItem->notifyCommand = notifyCommand;
475         newItem->track = trackList.at(i);
476         newItem->tracks = tracks;
477         newItem->device = device;
478         newItem->tags = tagList.at(i);
479         newItem->length = newItem->tags ? newItem->tags->length : 200.0f;
480         addTopLevelItem( newItem );
481         updateItem( newItem );
482         emit timeChanged( newItem->length );
483     }
484 
485     emit fileCountChanged( topLevelItemCount() );
486 
487     if( queue )
488         convertNextItem();
489 }
490 
updateItem(FileListItem * item)491 void FileList::updateItem( FileListItem *item )
492 {
493     if( !item )
494         return;
495 
496     // force repaint
497     item->setText( Column_State, "" );
498     item->setText( Column_Input, "" );
499     item->setText( Column_Output, "" );
500     item->setText( Column_Quality, "" );
501 
502     item->setToolTip( Column_State, "" );
503     item->setToolTip( Column_Input, "" );
504     item->setToolTip( Column_Output, "" );
505     item->setToolTip( Column_Quality, "" );
506 
507     // KUrl outputUrl;
508     // if( !item->outputUrl.toLocalFile().isEmpty() )
509     // {
510     //     outputUrl = item->outputUrl;
511     // }
512     // else
513     // {
514     //     outputUrl = OutputDirectory::calcPath( item, config );
515     // }
516     const KUrl outputUrl = OutputDirectory::calcPath( item, config );
517     item->setText( Column_Output, outputUrl.toLocalFile() );
518 
519     removeItemWidget( item, Column_State );
520     if( item->lInfo.data() )
521     {
522         disconnect( item->lInfo.data(), SIGNAL(linkActivated(const QString&)), this, 0 );
523         delete item->lInfo.data();
524     }
525 
526     const ConversionOptions *options = config->conversionOptionsManager()->getConversionOptions(item->conversionOptionsId);
527 
528     switch( item->state )
529     {
530         case FileListItem::WaitingForConversion:
531         {
532             if( QFile::exists(outputUrl.toLocalFile()) )
533             {
534                 item->setText( Column_State, i18n("Will be skipped") );
535             }
536             else if( config->data.general.copyIfSameCodec && options && item->codecName == options->codecName )
537             {
538                 item->setText( Column_State, i18n("Will be copied") );
539             }
540             else
541             {
542                 item->setText( Column_State, i18n("Waiting") );
543             }
544             break;
545         }
546         case FileListItem::ApplyingReplayGain:
547         {
548             item->setText( Column_State, i18n("Replay Gain") );
549             break;
550         }
551         case FileListItem::WaitingForAlbumGain:
552         {
553             item->setText( Column_State, i18n("Waiting for Replay Gain") );
554             break;
555         }
556         case FileListItem::ApplyingAlbumGain:
557         {
558             item->setText( Column_State, i18n("Replay Gain") );
559             break;
560         }
561         case FileListItem::Ripping:
562         {
563             item->setText( Column_State, i18n("Ripping") );
564             break;
565         }
566         case FileListItem::Converting:
567         {
568             item->setText( Column_State, i18n("Converting") );
569             break;
570         }
571         case FileListItem::Stopped:
572         {
573             switch( item->returnCode )
574             {
575                 case FileListItem::Succeeded:
576                 case FileListItem::SucceededWithProblems:
577                 {
578                     break;
579                 }
580                 case FileListItem::StoppedByUser:
581                 {
582                     item->setText( Column_State, i18nc("Short conversion status", "Stopped") );
583                     break;
584                 }
585                 case FileListItem::BackendNeedsConfiguration:
586                 {
587                     item->setText( Column_State, i18nc("Short conversion status", "Backend not configured") );
588                     break;
589                 }
590                 case FileListItem::DiscFull:
591                 {
592                     item->setText( Column_State, i18nc("Short conversion status", "Disc full") );
593                     break;
594                 }
595                 case FileListItem::CantWriteOutput:
596                 {
597                     item->lInfo = new QLabel( "<a href=\"" + QString::number(item->logId) + "\">" + i18nc("Short conversion status", "Can't write output") + "</a>" );
598                     connect( item->lInfo.data(), SIGNAL(linkActivated(const QString&)), this, SLOT(showLogClicked(const QString&)) );
599                     setItemWidget( item, Column_State, item->lInfo.data() );
600                     const QString toolTip = i18n("The conversion has failed.\nSee the log for more information.");
601                     item->setToolTip( Column_State, toolTip );
602                     item->setToolTip( Column_Input, toolTip );
603                     item->setToolTip( Column_Output, toolTip );
604                     item->setToolTip( Column_Quality, toolTip );
605                     break;
606                 }
607                 case FileListItem::Skipped:
608                 {
609                     item->setText( Column_State, i18n("Skipped") );
610                     break;
611                 }
612                 case FileListItem::Encrypted:
613                 {
614                     item->lInfo = new QLabel( "<a href=\"" + QString::number(item->logId) + "\">" + i18nc("Short conversion status", "File is encrypted") + "</a>" );
615                     connect( item->lInfo.data(), SIGNAL(linkActivated(const QString&)), this, SLOT(showLogClicked(const QString&)) );
616                     setItemWidget( item, Column_State, item->lInfo.data() );
617                     const QString toolTip = i18n("The conversion has failed.\nSee the log for more information.");
618                     item->setToolTip( Column_State, toolTip );
619                     item->setToolTip( Column_Input, toolTip );
620                     item->setToolTip( Column_Output, toolTip );
621                     item->setToolTip( Column_Quality, toolTip );
622                     break;
623                 }
624                 case FileListItem::Failed:
625                 {
626                     item->lInfo = new QLabel( "<a href=\"" + QString::number(item->logId) + "\">" + i18nc("Short conversion status", "Failed") + "</a>" );
627                     connect( item->lInfo.data(), SIGNAL(linkActivated(const QString&)), this, SLOT(showLogClicked(const QString&)) );
628                     setItemWidget( item, Column_State, item->lInfo.data() );
629                     const QString toolTip = i18n("The conversion has failed.\nSee the log for more information.");
630                     item->setToolTip( Column_State, toolTip );
631                     item->setToolTip( Column_Input, toolTip );
632                     item->setToolTip( Column_Output, toolTip );
633                     item->setToolTip( Column_Quality, toolTip );
634                     break;
635                 }
636             }
637             break;
638         }
639     }
640 
641     if( options )
642         item->setText( Column_Quality, options->profile );
643 
644     if( item->track >= 0 )
645     {
646         if( item->tags )
647         {
648             item->setText( Column_Input, QString().sprintf("%02i",item->tags->track) + " - " + item->tags->artist + " - " + item->tags->title );
649         }
650         else // shouldn't be possible
651         {
652             item->setText( Column_Input, i18n("CD track %1").arg(item->track) );
653         }
654     }
655     else
656     {
657         item->setText( Column_Input, item->url.pathOrUrl() );
658     }
659 }
660 
showLogClicked(const QString & logIdString)661 void FileList::showLogClicked( const QString& logIdString )
662 {
663     const int logId = logIdString.toInt();
664 
665     if( !logId )
666         return;
667 
668     emit showLog( logId );
669 }
670 
updateItems(QList<FileListItem * > items)671 void FileList::updateItems( QList<FileListItem*> items )
672 {
673     for( int i=0; i<items.size(); i++ )
674     {
675         updateItem( items.at(i) );
676     }
677 }
678 
updateAllItems()679 void FileList::updateAllItems()
680 {
681     for( int i=0; i<topLevelItemCount(); i++ )
682     {
683         updateItem( topLevelItem(i) );
684     }
685 }
686 
startConversion()687 void FileList::startConversion()
688 {
689     // iterate through all items and set the state to "Waiting"
690     for( int i=0; i<topLevelItemCount(); i++ )
691     {
692         FileListItem *item = topLevelItem( i );
693         if( item )
694         {
695             bool isStopped = false;
696             switch( item->state )
697             {
698                 case FileListItem::WaitingForConversion:
699                     break;
700                 case FileListItem::Ripping:
701                     break;
702                 case FileListItem::Converting:
703                     break;
704                 case FileListItem::ApplyingReplayGain:
705                     break;
706                 case FileListItem::WaitingForAlbumGain:
707                     break;
708                 case FileListItem::ApplyingAlbumGain:
709                     break;
710                 case FileListItem::Stopped:
711                     isStopped = true;
712                     break;
713             }
714             if( isStopped )
715             {
716                 item->state = FileListItem::WaitingForConversion;
717                 updateItem( item );
718             }
719         }
720     }
721 
722     queue = true;
723     emit queueModeChanged( queue );
724     emit conversionStarted();
725     convertNextItem();
726 }
727 
killConversion()728 void FileList::killConversion()
729 {
730     queue = false;
731     emit queueModeChanged( queue );
732 
733     for( int i=0; i<topLevelItemCount(); i++ )
734     {
735         FileListItem *item = topLevelItem( i );
736         if( item )
737         {
738             bool canKill = false;
739             switch( item->state )
740             {
741                 case FileListItem::WaitingForConversion:
742                     break;
743                 case FileListItem::Ripping:
744                     canKill = true;
745                     break;
746                 case FileListItem::Converting:
747                     canKill = true;
748                     break;
749                 case FileListItem::ApplyingReplayGain:
750                     canKill = true;
751                     break;
752                 case FileListItem::WaitingForAlbumGain:
753                     break;
754                 case FileListItem::ApplyingAlbumGain:
755                     break;
756                 case FileListItem::Stopped:
757                     break;
758             }
759             if( canKill )
760                 emit killItem( item );
761             }
762     }
763 }
764 
stopConversion()765 void FileList::stopConversion()
766 {
767     queue = false;
768     emit queueModeChanged( queue );
769 }
770 
continueConversion()771 void FileList::continueConversion()
772 {
773     queue = true;
774     emit queueModeChanged( queue );
775 
776     convertNextItem();
777 }
778 
convertNextItem()779 void FileList::convertNextItem()
780 {
781     if( !queue )
782         return;
783 
784     int count = convertingCount();
785     bool callItemsSelected = false;
786     QStringList devices;
787 
788     // look for tracks that are being ripped
789     for( int i=0; i<topLevelItemCount(); i++ )
790     {
791         FileListItem *item = topLevelItem( i );
792         if( item->state == FileListItem::Ripping )
793         {
794             devices += item->device;
795         }
796     }
797 
798     // look for waiting files
799     for( int i=0; i<topLevelItemCount() && count < config->data.general.numFiles; i++ )
800     {
801         FileListItem *item = topLevelItem( i );
802         if( item->state == FileListItem::WaitingForConversion )
803         {
804             if( item->track >= 0 && !devices.contains(item->device) )
805             {
806                 count++;
807                 devices += item->device;
808                 emit convertItem( item );
809                 if( selectedFiles.contains(item) )
810                     callItemsSelected = true;
811             }
812             else if( item->track < 0 )
813             {
814                 count++;
815                 emit convertItem( item );
816                 if( selectedFiles.contains(item) )
817                     callItemsSelected = true;
818             }
819         }
820     }
821 
822     if( callItemsSelected )
823         itemsSelected();
824 
825     if( count == 0 )
826         itemFinished( 0, FileListItem::Succeeded );
827 }
828 
waitingCount()829 int FileList::waitingCount()
830 {
831     int count = 0;
832 
833     for( int i=0; i<topLevelItemCount(); i++ )
834     {
835         FileListItem *item = topLevelItem( i );
836         if( item->state == FileListItem::WaitingForConversion )
837             count++;
838     }
839 
840     return count;
841 }
842 
convertingCount(bool includeWaiting)843 int FileList::convertingCount( bool includeWaiting ) // TODO use Convert
844 {
845     int count = 0;
846     QString albumName;
847 
848     for( int i=0; i<topLevelItemCount(); i++ )
849     {
850         FileListItem *item = topLevelItem( i );
851         if( item )
852         {
853             bool isConverting = false;
854             switch( item->state )
855             {
856                 case FileListItem::WaitingForConversion:
857                 case FileListItem::ApplyingAlbumGain:
858                 case FileListItem::Stopped:
859                 {
860                     break;
861                 }
862                 case FileListItem::WaitingForAlbumGain:
863                 {
864                     if( includeWaiting )
865                     {
866                         isConverting = true;
867                     }
868                     break;
869                 }
870                 case FileListItem::Ripping:
871                 case FileListItem::Converting:
872                 case FileListItem::ApplyingReplayGain:
873                 {
874                     isConverting = true;
875                     break;
876                 }
877             }
878             if( isConverting )
879                 count++;
880         }
881     }
882 
883     return count;
884 }
885 
886 // qulonglong FileList::spaceLeftForDirectory( const QString& dir )
887 // {
888 //     if( dir.isEmpty() )
889 //         return 0;
890 //
891 //     KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByPath( dir );
892 //     if( !mp )
893 //         return 0;
894 //
895 //     KDiskFreeSpaceInfo job = KDiskFreeSpaceInfo::freeSpaceInfo( mp->mountPoint() );
896 //     if( !job.isValid() )
897 //         return 0;
898 //
899 //     KIO::filesize_t mBSize = job.size() / 1024 / 1024;
900 //     KIO::filesize_t mBUsed = job.used() / 1024 / 1024;
901 //
902 //     return mBSize - mBUsed;
903 // }
904 
itemFinished(FileListItem * item,FileListItem::ReturnCode returnCode,bool waitingForAlbumGain)905 void FileList::itemFinished( FileListItem *item, FileListItem::ReturnCode returnCode, bool waitingForAlbumGain )
906 {
907     if( item )
908     {
909         if( waitingForAlbumGain )
910         {
911             item->state = FileListItem::WaitingForAlbumGain;
912 
913             item->returnCode = returnCode;
914             updateItem( item );
915         }
916         else if( returnCode == FileListItem::Succeeded || returnCode == FileListItem::SucceededWithProblems )
917         {
918             config->conversionOptionsManager()->removeConversionOptions( item->conversionOptionsId );
919             if( selectedFiles.contains(item) )
920                 itemsSelected();
921             delete item;
922             item = 0;
923         }
924         else
925         {
926             item->state = FileListItem::Stopped;
927 
928             item->returnCode = returnCode;
929             updateItem( item );
930         }
931     }
932 
933     // FIXME disabled until saving gets faster
934 //     save( false );
935 
936     if( queue && waitingCount() > 0 )
937     {
938         convertNextItem();
939     }
940     else if( convertingCount(true) == 0 )
941     {
942         queue = false;
943         save( false );
944         emit queueModeChanged( queue );
945 //         float time = 0;
946 //         for( int i=0; i<topLevelItemCount(); i++ )
947 //         {
948 //             FileListItem *temp_item = topLevelItem( i );
949 //             updateItem( temp_item ); // TODO why?
950 //             time += temp_item->length;
951 //         }
952 //         emit finished( time );
953         emit finished( true );
954         emit conversionStopped( returnCode != FileListItem::Succeeded );
955         emit fileCountChanged( topLevelItemCount() );
956     }
957 }
958 
waitForAlbumGain(FileListItem * item)959 bool FileList::waitForAlbumGain( FileListItem *item ) // TODO was ist wenn die datei unten drunter schon fertig ist, was ist wenn eine datei oben drpber noch nicht fertig ist
960 {
961     if( !config->data.general.waitForAlbumGain )
962         return false;
963 
964     if( !item || !item->tags )
965         return false;
966 
967     if( item->tags->album.isEmpty() )
968         return false;
969 
970     FileListItem *nextItem;
971 
972     nextItem = item;
973     while( ( nextItem = static_cast<FileListItem*>(itemAbove(nextItem)) ) )
974     {
975         const ConversionOptions* conversionOptions = config->conversionOptionsManager()->getConversionOptions(nextItem->conversionOptionsId);
976         if( nextItem->tags && nextItem->tags->album == item->tags->album && conversionOptions && conversionOptions->replaygain )
977         {
978             if( nextItem->state != FileListItem::WaitingForAlbumGain && nextItem->state != FileListItem::Stopped )
979             {
980                 return true;
981             }
982         }
983         else
984         {
985             break;
986         }
987     }
988 
989     nextItem = item;
990     while( ( nextItem = static_cast<FileListItem*>(itemBelow(nextItem)) ) )
991     {
992         const ConversionOptions* conversionOptions = config->conversionOptionsManager()->getConversionOptions(nextItem->conversionOptionsId);
993         if( nextItem->tags && nextItem->tags->album == item->tags->album && conversionOptions && conversionOptions->replaygain )
994         {
995             if( nextItem->state != FileListItem::WaitingForAlbumGain && nextItem->state != FileListItem::Stopped )
996             {
997                 return true;
998             }
999         }
1000         else
1001         {
1002             break;
1003         }
1004     }
1005 
1006     return false;
1007 }
1008 
rippingFinished(const QString & device)1009 void FileList::rippingFinished( const QString& device )
1010 {
1011     if( waitingCount() > 0 && queue )
1012     {
1013         // look for waiting files
1014         for( int i=0; i<topLevelItemCount(); i++ )
1015         {
1016             FileListItem *item = topLevelItem( i );
1017             if( item->state == FileListItem::WaitingForConversion )
1018             {
1019                 if( item->track >= 0 && item->device == device )
1020                 {
1021                     // rip next track
1022                     emit convertItem( item );
1023                     if( selectedFiles.contains(item) )
1024                         itemsSelected();
1025                     return;
1026                 }
1027             }
1028         }
1029     }
1030 
1031     // no more track from that device found
1032     if( config->data.advanced.ejectCdAfterRip )
1033     {
1034         QList<Solid::Device> solidDevices = Solid::Device::listFromType(Solid::DeviceInterface::OpticalDrive, QString());
1035         foreach( Solid::Device solidDevice, solidDevices )
1036         {
1037             Solid::OpticalDrive *opticalDrive = solidDevice.as<Solid::OpticalDrive>();
1038             if( opticalDrive )
1039             {
1040                 Solid::Block *block = solidDevice.as<Solid::Block>();
1041                 if( block && block->device() == device )
1042                 {
1043                     opticalDrive->eject();
1044                     break;
1045                 }
1046             }
1047         }
1048     }
1049 }
1050 
showContextMenu(const QPoint & point)1051 void FileList::showContextMenu( const QPoint& point )
1052 {
1053     const FileListItem * const item = static_cast<FileListItem*>(itemAt( point ));
1054 
1055     // if item is null, we can abort here
1056     if( !item )
1057         return;
1058 
1059     // add a tilte to our context manu
1060     //contextMenu->insertTitle( static_cast<FileListItem*>(item)->fileName ); // TODO sqeeze or something else
1061 
1062     // TODO implement pasting, etc.
1063 
1064     contextMenu->clear();
1065 
1066     // is this file (of our item) beeing converted at the moment?
1067     bool isConverting = false;
1068     switch( item->state )
1069     {
1070         case FileListItem::WaitingForConversion:
1071             break;
1072         case FileListItem::Ripping:
1073             isConverting = true;
1074             break;
1075         case FileListItem::Converting:
1076             isConverting = true;
1077             break;
1078         case FileListItem::ApplyingReplayGain:
1079             isConverting = true;
1080             break;
1081         case FileListItem::WaitingForAlbumGain:
1082             break;
1083         case FileListItem::ApplyingAlbumGain:
1084             break;
1085         case FileListItem::Stopped:
1086             break;
1087     }
1088     if( item->state == FileListItem::WaitingForAlbumGain )
1089     {
1090         contextMenu->addAction( removeAction );
1091     }
1092     else if( isConverting )
1093     {
1094         //contextMenu->addAction( paste );
1095         //contextMenu->addSeparator();
1096         contextMenu->addAction( stopAction );
1097     }
1098     else
1099     {
1100         contextMenu->addAction( editAction );
1101         contextMenu->addSeparator();
1102         contextMenu->addAction( removeAction );
1103         //contextMenu->addAction( paste );
1104         contextMenu->addSeparator();
1105         contextMenu->addAction( startAction );
1106     }
1107 
1108     // show the popup menu
1109     contextMenu->popup( viewport()->mapToGlobal(point) );
1110 }
1111 
showOptionsEditorDialog()1112 void FileList::showOptionsEditorDialog()
1113 {
1114     if( !optionsEditor )
1115     {
1116         optionsEditor = new OptionsEditor( config, this );
1117         if( !optionsEditor )
1118         {
1119              // TODO error message
1120             return;
1121         }
1122         connect( this, SIGNAL(editItems(QList<FileListItem*>)), optionsEditor, SLOT(itemsSelected(QList<FileListItem*>)) );
1123         connect( this, SIGNAL(setPreviousItemEnabled(bool)), optionsEditor, SLOT(setPreviousEnabled(bool)) );
1124         connect( this, SIGNAL(setNextItemEnabled(bool)), optionsEditor, SLOT(setNextEnabled(bool)) );
1125         connect( this, SIGNAL(itemRemoved(FileListItem*)), optionsEditor, SLOT(itemRemoved(FileListItem*)) );
1126         connect( optionsEditor, SIGNAL(user2Clicked()), this, SLOT(selectPreviousItem()) );
1127         connect( optionsEditor, SIGNAL(user1Clicked()), this, SLOT(selectNextItem()) );
1128         connect( optionsEditor, SIGNAL(updateFileListItems(QList<FileListItem*>)), this, SLOT(updateItems(QList<FileListItem*>)) );
1129     }
1130     itemsSelected();
1131     optionsEditor->show();
1132 }
1133 
selectPreviousItem()1134 void FileList::selectPreviousItem()
1135 {
1136     QTreeWidgetItem * const item = itemAbove( selectedItems().first() );
1137 
1138     if( !item )
1139         return;
1140 
1141     disconnect( this, SIGNAL(itemSelectionChanged()), 0, 0 ); // avoid backfireing
1142 
1143     for( int i=0; i<selectedFiles.count(); i++ )
1144     {
1145         selectedFiles.at(i)->setSelected( false );
1146     }
1147 
1148     item->setSelected( true );
1149     scrollToItem( item );
1150 
1151     connect( this, SIGNAL(itemSelectionChanged()), this, SLOT(itemsSelected()) );
1152 
1153     itemsSelected();
1154 }
1155 
selectNextItem()1156 void FileList::selectNextItem()
1157 {
1158     QTreeWidgetItem * const item = itemBelow( selectedItems().last() );
1159 
1160     if( !item )
1161         return;
1162 
1163     disconnect( this, SIGNAL(itemSelectionChanged()), 0, 0 ); // avoid backfireing
1164 
1165     for( int i=0; i<selectedFiles.count(); i++ )
1166     {
1167         selectedFiles.at(i)->setSelected( false );
1168     }
1169 
1170     item->setSelected( true );
1171     scrollToItem( item );
1172 
1173     connect( this, SIGNAL(itemSelectionChanged()), this, SLOT(itemsSelected()) );
1174 
1175     itemsSelected();
1176 }
1177 
removeSelectedItems()1178 void FileList::removeSelectedItems()
1179 {
1180     QList<QTreeWidgetItem*> items = selectedItems();
1181     for( int i=0; i<items.size(); i++ )
1182     {
1183         FileListItem *item = static_cast<FileListItem*>(items.at(i));
1184         if( item && item->isSelected() )
1185         {
1186             bool canRemove = false;
1187             switch( item->state )
1188             {
1189                 case FileListItem::WaitingForConversion:
1190                     canRemove = true;
1191                     break;
1192                 case FileListItem::Ripping:
1193                     break;
1194                 case FileListItem::Converting:
1195                     break;
1196                 case FileListItem::ApplyingReplayGain:
1197                     break;
1198                 case FileListItem::WaitingForAlbumGain:
1199                     canRemove = true;
1200                     break;
1201                 case FileListItem::ApplyingAlbumGain:
1202                     break;
1203                 case FileListItem::Stopped:
1204                     canRemove = true;
1205                     break;
1206             }
1207             if( canRemove )
1208             {
1209                 emit timeChanged( -item->length );
1210                 config->conversionOptionsManager()->removeConversionOptions( item->conversionOptionsId );
1211                 delete item;
1212             }
1213         }
1214     }
1215     emit fileCountChanged( topLevelItemCount() );
1216 
1217     itemsSelected();
1218 }
1219 
convertSelectedItems()1220 void FileList::convertSelectedItems()
1221 {
1222     bool started = false;
1223     bool callItemsSelected = false;
1224     QList<QTreeWidgetItem*> items = selectedItems();
1225     for( int i=0; i<items.size(); i++ )
1226     {
1227         FileListItem *item = static_cast<FileListItem*>(items.at(i));
1228         if( item )
1229         {
1230             bool canConvert = false;
1231             switch( item->state )
1232             {
1233                 case FileListItem::WaitingForConversion:
1234                     canConvert = true;
1235                     break;
1236                 case FileListItem::Ripping:
1237                     break;
1238                 case FileListItem::Converting:
1239                     break;
1240                 case FileListItem::ApplyingReplayGain:
1241                     break;
1242                 case FileListItem::WaitingForAlbumGain:
1243                     break;
1244                 case FileListItem::ApplyingAlbumGain:
1245                     break;
1246                 case FileListItem::Stopped:
1247                     canConvert = true;
1248                     break;
1249             }
1250             if( canConvert )
1251             {
1252                 // emit conversionStarted first because in case the plugin reports an error the conversion stop immediately
1253                 // and the itemFinished slot gets called. If conversionStarted was emitted at the end of this function
1254                 // it might broadcast the message that the conversion has started after the conversion already stopped
1255                 if( !started )
1256                     emit conversionStarted();
1257 
1258                 emit convertItem( item );
1259 
1260                 if( selectedFiles.contains(item) )
1261                     callItemsSelected = true;
1262 
1263                 started = true;
1264             }
1265         }
1266     }
1267 
1268     if( callItemsSelected )
1269         itemsSelected();
1270 }
1271 
killSelectedItems()1272 void FileList::killSelectedItems()
1273 {
1274     QList<QTreeWidgetItem*> items = selectedItems();
1275     for( int i=0; i<items.size(); i++ )
1276     {
1277         FileListItem *item = static_cast<FileListItem*>(items.at(i));
1278         if( item )
1279         {
1280             bool canKill = false;
1281             switch( item->state )
1282             {
1283                 case FileListItem::WaitingForConversion:
1284                     break;
1285                 case FileListItem::Ripping:
1286                     canKill = true;
1287                     break;
1288                 case FileListItem::Converting:
1289                     canKill = true;
1290                     break;
1291                 case FileListItem::ApplyingReplayGain:
1292                     canKill = true;
1293                     break;
1294                 case FileListItem::WaitingForAlbumGain:
1295                     break;
1296                 case FileListItem::ApplyingAlbumGain:
1297                     break;
1298                 case FileListItem::Stopped:
1299                     break;
1300             }
1301             if( canKill )
1302                 emit killItem( item );
1303         }
1304     }
1305 }
1306 
itemsSelected()1307 void FileList::itemsSelected()
1308 {
1309     selectedFiles.clear();
1310 
1311     QList<QTreeWidgetItem*> items = selectedItems();
1312     foreach( QTreeWidgetItem* item, items )
1313     {
1314         selectedFiles.append( static_cast<FileListItem*>(item) );
1315     }
1316 
1317     if( selectedFiles.count() > 0 )
1318     {
1319         emit setPreviousItemEnabled( itemAbove(selectedFiles.first()) != 0 );
1320         emit setNextItemEnabled( itemBelow(selectedFiles.last()) != 0 );
1321     }
1322     else
1323     {
1324         emit setPreviousItemEnabled( false );
1325         emit setNextItemEnabled( false );
1326     }
1327 
1328     emit editItems( selectedFiles );
1329 }
1330 
load(bool user)1331 void FileList::load( bool user )
1332 {
1333     const QString fileListPath = user ? "filelist.xml" : "filelist_autosave.xml";
1334     load( KStandardDirs::locateLocal("data","soundkonverter/"+fileListPath) );
1335 }
1336 
load(const QString & fileListPath)1337 void FileList::load( const QString& fileListPath )
1338 {
1339     if( topLevelItemCount() > 0 )
1340     {
1341         const int ret = KMessageBox::questionYesNo( this, i18n("Do you want to overwrite the current file list?\n\nIf not, the saved file list will be appended.") );
1342         if( ret == KMessageBox::Yes )
1343         {
1344             for( int i=0; i<topLevelItemCount(); i++ )
1345             {
1346                 FileListItem *item = topLevelItem(i);
1347                 if( item )
1348                 {
1349                     bool canRemove = false;
1350                     switch( item->state )
1351                     {
1352                         case FileListItem::WaitingForConversion:
1353                             canRemove = true;
1354                             break;
1355                         case FileListItem::Ripping:
1356                             break;
1357                         case FileListItem::Converting:
1358                             break;
1359                         case FileListItem::ApplyingReplayGain:
1360                             break;
1361                         case FileListItem::WaitingForAlbumGain:
1362                             break;
1363                         case FileListItem::ApplyingAlbumGain:
1364                             break;
1365                         case FileListItem::Stopped:
1366                             canRemove = true;
1367                             break;
1368                     }
1369                     if( canRemove )
1370                     {
1371                         config->conversionOptionsManager()->removeConversionOptions( item->conversionOptionsId );
1372                         delete item;
1373                         i--;
1374                     }
1375                 }
1376             }
1377 
1378             itemsSelected();
1379         }
1380     }
1381 
1382     QMap<int,int> conversionOptionsIds;
1383     QMap<int,int> conversionOptionsReferences;
1384     QFile listFile( fileListPath );
1385     if( listFile.open( QIODevice::ReadOnly ) )
1386     {
1387         QDomDocument list("soundkonverter_filelist");
1388         if( list.setContent( &listFile ) )
1389         {
1390             QDomElement root = list.documentElement();
1391             if( root.nodeName() == "soundkonverter" && root.attribute("type") == "filelist" )
1392             {
1393                 QDomNodeList conversionOptionsElements = root.elementsByTagName("conversionOptions");
1394                 for( int i=0; i<conversionOptionsElements.count(); i++ )
1395                 {
1396                     QList<QDomElement> filterOptionsElements;
1397                     const QString pluginName = conversionOptionsElements.at(i).toElement().attribute("pluginName");
1398                     CodecPlugin *plugin = qobject_cast<CodecPlugin*>(config->pluginLoader()->backendPluginByName( pluginName ));
1399                     if( plugin )
1400                     {
1401                         ConversionOptions *conversionOptions = plugin->conversionOptionsFromXml( conversionOptionsElements.at(i).toElement(), &filterOptionsElements );
1402                         if( conversionOptions )
1403                         {
1404                             foreach( const QDomElement& filterOptionsElement, filterOptionsElements )
1405                             {
1406                                 FilterOptions *filterOptions = 0;
1407                                 const QString filterPluginName = filterOptionsElement.attribute("pluginName");
1408                                 FilterPlugin *filterPlugin = qobject_cast<FilterPlugin*>(config->pluginLoader()->backendPluginByName( filterPluginName ));
1409                                 if( filterPlugin )
1410                                 {
1411                                     filterOptions = filterPlugin->filterOptionsFromXml( filterOptionsElement );
1412                                 }
1413                                 else
1414                                 {
1415                                     continue;
1416                                 }
1417                                 conversionOptions->filterOptions.append( filterOptions );
1418                             }
1419                             const int id = conversionOptionsElements.at(i).toElement().attribute("id").toInt();
1420                             conversionOptionsIds[id] = config->conversionOptionsManager()->addConversionOptions( conversionOptions );
1421                             conversionOptionsReferences[conversionOptionsIds[id]] = 0;
1422                         }
1423                     }
1424                 }
1425                 QDomNodeList files = root.elementsByTagName("file");
1426                 pScanStatus->setValue( 0 );
1427                 pScanStatus->setMaximum( files.count() );
1428                 pScanStatus->show();
1429                 tScanStatus.start();
1430                 for( int i=0; i<files.count(); i++ )
1431                 {
1432                     QDomElement file = files.at(i).toElement();
1433                     FileListItem *item = new FileListItem( this );
1434                     item->url = KUrl(file.attribute("url"));
1435                     // item->outputUrl = KUrl(file.attribute("outputUrl"));
1436                     item->codecName = file.attribute("codecName");
1437                     item->conversionOptionsId = conversionOptionsIds[file.attribute("conversionOptionsId").toInt()]; // TODO check if id exists
1438                     item->local = file.attribute("local").toInt();
1439                     item->track = file.attribute("track").toInt();
1440                     item->tracks = file.attribute("tracks").toInt();
1441                     item->device = file.attribute("device");
1442                     item->length = file.attribute("time").toInt();
1443                     item->notifyCommand = file.attribute("notifyCommand");
1444                     if( conversionOptionsReferences[item->conversionOptionsId] != 0 )
1445                         config->conversionOptionsManager()->increaseReferences( item->conversionOptionsId );
1446                     conversionOptionsReferences[item->conversionOptionsId]++;
1447                     if( file.elementsByTagName("tags").count() > 0 )
1448                     {
1449                         QDomElement tags = file.elementsByTagName("tags").at(0).toElement();
1450                         item->tags = new TagData();
1451                         item->tags->artist = tags.attribute("artist");
1452                         item->tags->albumArtist = tags.attribute("albumArtist");
1453                         item->tags->composer = tags.attribute("composer");
1454                         item->tags->album = tags.attribute("album");
1455                         item->tags->title = tags.attribute("title");
1456                         item->tags->genre = tags.attribute("genre");
1457                         item->tags->comment = tags.attribute("comment");
1458                         item->tags->track = tags.attribute("track").toInt();
1459                         item->tags->trackTotal = tags.attribute("trackTotal").toInt();
1460                         item->tags->disc = tags.attribute("disc").toInt();
1461                         item->tags->discTotal = tags.attribute("discTotal").toInt();
1462                         item->tags->year = tags.attribute("year").toInt();
1463                         item->tags->trackGain = tags.attribute("track_gain").toFloat();
1464                         item->tags->albumGain = tags.attribute("album_gain").toFloat();
1465                         item->tags->length = tags.attribute("length").toInt();
1466                         item->tags->samplingRate = tags.attribute("samplingRate").toInt();
1467                         item->tags->isEncrypted = tags.attribute("isEncrypted").toInt();
1468                     }
1469                     addTopLevelItem( item );
1470                     updateItem( item );
1471                     emit timeChanged( item->length );
1472                     if( tScanStatus.elapsed() > ConfigUpdateDelay * 10 )
1473                     {
1474                         pScanStatus->setValue( i );
1475                         tScanStatus.start();
1476                     }
1477                 }
1478                 pScanStatus->hide();
1479             }
1480         }
1481         listFile.close();
1482         emit fileCountChanged( topLevelItemCount() );
1483     }
1484 }
1485 
save(bool user)1486 void FileList::save( bool user )
1487 {
1488     // assume it's a misclick if there's nothing to save
1489     if( user && topLevelItemCount() == 0 )
1490         return;
1491 
1492     QTime time;
1493     time.start();
1494 
1495     QDomDocument list("soundkonverter_filelist");
1496     QDomElement root = list.createElement("soundkonverter");
1497     root.setAttribute("type","filelist");
1498     list.appendChild(root);
1499 
1500     foreach( const int id, config->conversionOptionsManager()->getAllIds() )
1501     {
1502         if( const ConversionOptions* options = config->conversionOptionsManager()->getConversionOptions(id) )
1503         {
1504             QDomElement conversionOptions = options->toXml(list);
1505             conversionOptions.setAttribute("id",id);
1506             root.appendChild(conversionOptions);
1507         }
1508         else
1509         {
1510             // FIXME error message, null pointer for conversion options
1511         }
1512     }
1513 
1514     for( int i=0; i<topLevelItemCount(); i++ )
1515     {
1516         const FileListItem *item = topLevelItem(i);
1517 
1518         QDomElement file = list.createElement("file");
1519         file.setAttribute("url",item->url.pathOrUrl());
1520         // file.setAttribute("outputUrl",item->outputUrl.pathOrUrl());
1521         file.setAttribute("codecName",item->codecName);
1522         file.setAttribute("conversionOptionsId",item->conversionOptionsId);
1523         file.setAttribute("local",item->local);
1524         file.setAttribute("track",item->track);
1525         file.setAttribute("tracks",item->tracks);
1526         file.setAttribute("device",item->device);
1527         file.setAttribute("time",item->length);
1528         file.setAttribute("notifyCommand",item->notifyCommand);
1529         root.appendChild(file);
1530         if( item->tags )
1531         {
1532             QDomElement tags = list.createElement("tags");
1533             tags.setAttribute("artist",item->tags->artist);
1534             tags.setAttribute("albumArtist",item->tags->albumArtist);
1535             tags.setAttribute("composer",item->tags->composer);
1536             tags.setAttribute("album",item->tags->album);
1537             tags.setAttribute("title",item->tags->title);
1538             tags.setAttribute("genre",item->tags->genre);
1539             tags.setAttribute("comment",item->tags->comment);
1540             tags.setAttribute("track",item->tags->track);
1541             tags.setAttribute("trackTotal",item->tags->trackTotal);
1542             tags.setAttribute("disc",item->tags->disc);
1543             tags.setAttribute("discTotal",item->tags->discTotal);
1544             tags.setAttribute("year",item->tags->year);
1545             tags.setAttribute("track_gain",item->tags->trackGain);
1546             tags.setAttribute("album_gain",item->tags->albumGain);
1547             tags.setAttribute("length",item->tags->length);
1548             tags.setAttribute("samplingRate",item->tags->samplingRate);
1549             tags.setAttribute("isEncrypted",item->tags->isEncrypted);
1550             file.appendChild(tags);
1551         }
1552     }
1553 
1554     const QString fileName = user ? "filelist.xml" : "filelist_autosave.xml";
1555     QFile listFile( KStandardDirs::locateLocal("data","soundkonverter/"+fileName) );
1556     if( listFile.open( QIODevice::WriteOnly ) )
1557     {
1558         QTextStream stream(&listFile);
1559         stream << list.toString();
1560         listFile.close();
1561     }
1562 
1563     logger->log( 1000, QString("Saving the file list took %1 ms").arg(time.elapsed()) );
1564 }
1565 
1566 
1567