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