1 
2 #include "convert.h"
3 #include "convertitem.h"
4 #include "config.h"
5 #include "core/codecplugin.h"
6 #include "core/conversionoptions.h"
7 #include "filelist.h"
8 #include "global.h"
9 #include "logger.h"
10 #include "outputdirectory.h"
11 
12 #include <kio/jobclasses.h>
13 #include <kio/job.h>
14 
15 #include <KLocale>
16 #include <KMessageBox>
17 #include <QFile>
18 #include <QFileInfo>
19 
20 
Convert(Config * _config,FileList * _fileList,Logger * _logger,QObject * parent)21 Convert::Convert( Config *_config, FileList *_fileList, Logger *_logger, QObject *parent )
22     : QObject( parent ),
23     config( _config ),
24     fileList( _fileList ),
25     logger( _logger )
26 {
27     connect( &updateTimer, SIGNAL(timeout()), this, SLOT(updateProgress()) );
28 
29     QList<CodecPlugin*> codecPlugins = config->pluginLoader()->getAllCodecPlugins();
30     for( int i=0; i<codecPlugins.size(); i++ )
31     {
32         connect( codecPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
33         connect( codecPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
34     }
35     QList<FilterPlugin*> filterPlugins = config->pluginLoader()->getAllFilterPlugins();
36     for( int i=0; i<filterPlugins.size(); i++ )
37     {
38         connect( filterPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
39         connect( filterPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
40     }
41     QList<ReplayGainPlugin*> replaygainPlugins = config->pluginLoader()->getAllReplayGainPlugins();
42     for( int i=0; i<replaygainPlugins.size(); i++ )
43     {
44         connect( replaygainPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
45         connect( replaygainPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
46     }
47     QList<RipperPlugin*> ripperPlugins = config->pluginLoader()->getAllRipperPlugins();
48     for( int i=0; i<ripperPlugins.size(); i++ )
49     {
50         connect( ripperPlugins.at(i), SIGNAL(jobFinished(int,int)), this, SLOT(pluginProcessFinished(int,int)) );
51         connect( ripperPlugins.at(i), SIGNAL(log(int,const QString&)), this, SLOT(pluginLog(int,const QString&)) );
52     }
53 }
54 
~Convert()55 Convert::~Convert()
56 {}
57 
cleanUp()58 void Convert::cleanUp()
59 {
60     // TODO clean up
61 }
62 
get(ConvertItem * item)63 void Convert::get( ConvertItem *item )
64 {
65     if( item->take > 0 )
66         remove( item, FileListItem::Failed );
67 
68     logger->log( item->logID, i18n("Getting file") );
69     item->state = ConvertItem::get;
70 
71     item->tempInputUrl = item->generateTempUrl( "download", item->inputUrl.fileName().mid(item->inputUrl.fileName().lastIndexOf(".")+1) );
72 
73     if( !updateTimer.isActive() )
74         updateTimer.start( ConfigUpdateDelay );
75 
76     logger->log( item->logID, i18n("Copying \"%1\" to \"%2\"",item->inputUrl.pathOrUrl(),item->tempInputUrl.toLocalFile()) );
77 
78     item->kioCopyJob = KIO::file_copy( item->inputUrl, item->tempInputUrl, -1 , KIO::HideProgressInfo );
79     connect( item->kioCopyJob.data(), SIGNAL(result(KJob*)), this, SLOT(kioJobFinished(KJob*)) );
80     connect( item->kioCopyJob.data(), SIGNAL(percent(KJob*,unsigned long)), this, SLOT(kioJobProgress(KJob*,unsigned long)) );
81 }
82 
convert(ConvertItem * item)83 void Convert::convert( ConvertItem *item )
84 {
85     if( !item )
86         return;
87 
88     const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions( item->fileListItem->conversionOptionsId );
89     if( !conversionOptions )
90         return;
91 
92     KUrl inputUrl;
93     if( !item->tempInputUrl.toLocalFile().isEmpty() )
94         inputUrl = item->tempInputUrl;
95     else
96         inputUrl = item->inputUrl;
97 
98     if( !item->fileListItem->tags )
99         item->fileListItem->tags = config->tagEngine()->readTags( inputUrl );
100 
101     if( item->fileListItem->tags && item->fileListItem->tags->isEncrypted )
102     {
103         logger->log( item->logID, "<br><span style=\"color:#C00000\">" + i18n("File is encrypted, conversion not possible") + "</span>" );
104         remove( item, FileListItem::Encrypted );
105         return;
106     }
107 
108     if( item->outputUrl.isEmpty() )
109     {
110         // item->outputUrl = !item->fileListItem->outputUrl.url().isEmpty() ? item->fileListItem->outputUrl : OutputDirectory::calcPath( item->fileListItem, config, usedOutputNames.values() );
111         item->outputUrl = OutputDirectory::calcPath( item->fileListItem, config, usedOutputNames.values() );
112         if( QFile::exists(item->outputUrl.toLocalFile()) )
113         {
114             logger->log( item->logID, "\tOutput file already exists" );
115             item->outputUrl = KUrl();
116             remove( item, FileListItem::Skipped );
117             return;
118         }
119         if( OutputDirectory::makePath(item->outputUrl) == KUrl() )
120         {
121             logger->log( item->logID, "\t" + i18n("Cannot create output directory \"%1\"",item->outputUrl.toLocalFile()) );
122             item->outputUrl = KUrl();
123             remove( item, FileListItem::CantWriteOutput );
124             return;
125         }
126         fileList->updateItem( item->fileListItem );
127     }
128     usedOutputNames.insert( item->logID, item->outputUrl.toLocalFile() );
129 
130     if( config->data.general.copyIfSameCodec && item->fileListItem->codecName == conversionOptions->codecName )
131     {
132         item->state = ConvertItem::convert;
133         logger->log( item->logID, i18n("Copying \"%1\" to \"%2\"",inputUrl.pathOrUrl(),item->outputUrl.toLocalFile()) );
134 
135         item->kioCopyJob = KIO::file_copy( item->inputUrl, item->outputUrl, -1 , KIO::HideProgressInfo );
136         connect( item->kioCopyJob.data(), SIGNAL(result(KJob*)), this, SLOT(kioJobFinished(KJob*)) );
137 //         connect( item->kioCopyJob.data(), SIGNAL(percent(KJob*,unsigned long)), this, SLOT(kioJobProgress(KJob*,unsigned long)) );
138 
139         return;
140     }
141 
142     if( item->take > item->conversionPipes.count() - 1 )
143     {
144         logger->log( item->logID, "\t" + i18n("No more backends left to try :(") );
145         remove( item, FileListItem::Failed );
146         return;
147     }
148 
149     if( item->take > 0 )
150         item->updateTimes();
151 
152     item->conversionPipesStep = -1;
153 
154     if( !updateTimer.isActive() )
155         updateTimer.start( ConfigUpdateDelay );
156 
157     if( item->conversionPipes.at(item->take).trunks.count() == 1 ) // conversion can be done by one plugin alone
158     {
159         logger->log( item->logID, i18n("Converting") );
160         item->state = ConvertItem::convert;
161         item->conversionPipesStep = 0;
162         item->backendPlugin = item->conversionPipes.at(item->take).trunks.at(0).plugin;
163         if( item->backendPlugin->type() == "codec" || item->backendPlugin->type() == "filter" )
164         {
165             bool useInternalReplayGain = false;
166             if( item->conversionPipes.at(item->take).trunks.at(0).data.hasInternalReplayGain && item->mode & ConvertItem::replaygain )
167             {
168                 foreach( const Config::CodecData& codecData, config->data.backends.codecs )
169                 {
170                     if( codecData.codecName == item->conversionPipes.at(item->take).trunks.at(0).codecTo )
171                     {
172                         if( codecData.replaygain.first() == i18n("Try internal") )
173                             useInternalReplayGain = true;
174 
175                         break;
176                     }
177                 }
178             }
179             if( useInternalReplayGain )
180             {
181                 item->internalReplayGainUsed = true;
182             }
183             item->backendID = qobject_cast<CodecPlugin*>(item->backendPlugin)->convert( inputUrl, item->outputUrl, item->conversionPipes.at(item->take).trunks.at(0).codecFrom, item->conversionPipes.at(item->take).trunks.at(0).codecTo, conversionOptions, item->fileListItem->tags, useInternalReplayGain );
184         }
185         else if( item->backendPlugin->type() == "ripper" )
186         {
187             item->backendID = qobject_cast<RipperPlugin*>(item->backendPlugin)->rip( item->fileListItem->device, item->fileListItem->track, item->fileListItem->tracks, item->outputUrl );
188         }
189 
190         if( item->backendID < 100 )
191         {
192             switch( item->backendID )
193             {
194                 case BackendPlugin::BackendNeedsConfiguration:
195                 {
196                     logger->log( item->logID, "\t" + i18n("Conversion failed. At least one of the used backends needs to be configured properly.") );
197                     remove( item, FileListItem::BackendNeedsConfiguration );
198                     break;
199                 }
200                 case BackendPlugin::FeatureNotSupported:
201                 {
202                     logger->log( item->logID, "\t" + i18n("Conversion failed. The preferred plugin lacks support for a necessary feature.") );
203                     executeSameStep( item );
204                     break;
205                 }
206                 case BackendPlugin::UnknownError:
207                 {
208                     logger->log( item->logID, "\t" + i18n("Conversion failed. Unknown Error.") );
209                     executeSameStep( item );
210                     break;
211                 }
212             }
213         }
214         else if( item->backendPlugin->type() == "ripper" )
215         {
216             item->fileListItem->state = FileListItem::Ripping;
217         }
218     }
219     else // conversion needs two plugins or more
220     {
221         bool usePipes = false;
222         bool useInternalReplayGain = false;
223         QStringList commandList;
224         if( config->data.advanced.usePipes )
225         {
226             usePipes = true;
227 
228             const int stepCount = item->conversionPipes.at(item->take).trunks.count() - 1;
229             int step = 0;
230             foreach( const ConversionPipeTrunk& trunk, item->conversionPipes.at(item->take).trunks )
231             {
232                 BackendPlugin *plugin = trunk.plugin;
233                 QStringList command;
234                 const KUrl inUrl = ( step == 0 ) ? inputUrl : KUrl();
235                 const KUrl outUrl = ( step == stepCount ) ? item->outputUrl : KUrl();
236                 if( plugin->type() == "codec" || plugin->type() == "filter" )
237                 {
238                     if( step == stepCount && trunk.data.hasInternalReplayGain && item->mode & ConvertItem::replaygain )
239                     {
240                         foreach( const Config::CodecData& codecData, config->data.backends.codecs )
241                         {
242                             if( codecData.codecName == trunk.codecTo )
243                             {
244                                 if( codecData.replaygain.first() == i18n("Try internal") )
245                                     useInternalReplayGain = true;
246 
247                                 break;
248                             }
249                         }
250                     }
251                     command = qobject_cast<CodecPlugin*>(plugin)->convertCommand( inUrl, outUrl, item->conversionPipes.at(item->take).trunks.at(step).codecFrom, item->conversionPipes.at(item->take).trunks.at(step).codecTo, conversionOptions, item->fileListItem->tags, useInternalReplayGain );
252                 }
253                 else if( plugin->type() == "ripper" )
254                 {
255                     command = qobject_cast<RipperPlugin*>(plugin)->ripCommand( item->fileListItem->device, item->fileListItem->track, item->fileListItem->tracks, outUrl );
256                 }
257                 if( command.isEmpty() )
258                 {
259                     usePipes = false;
260                     break;
261                 }
262                 commandList.append( command.join(" ") );
263                 step++;
264             }
265         }
266         if( usePipes )
267         {
268             // both plugins support pipes
269             const QString command = commandList.join(" | ");
270 
271             if( useInternalReplayGain )
272                 item->internalReplayGainUsed = true;
273 
274             logger->log( item->logID, i18n("Converting") );
275             item->state = ConvertItem::convert;
276             // merge all conversion times into one since we are doing everything in one step
277             float time = 0.0f;
278             foreach( const float t, item->convertTimes )
279             {
280                 time += t;
281             }
282             item->convertTimes.clear();
283             item->convertTimes.append( time );
284             item->conversionPipesStep = 0;
285             logger->log( item->logID, "<pre>\t<span style=\"color:#DC6300\">" + command + "</span></pre>" );
286             item->process = new KProcess();
287             item->process.data()->setOutputChannelMode( KProcess::MergedChannels );
288             connect( item->process.data(), SIGNAL(readyRead()), this, SLOT(processOutput()) );
289             connect( item->process.data(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processExit(int,QProcess::ExitStatus)) );
290             item->process.data()->clearProgram();
291             item->process.data()->setShellCommand( command );
292             item->process.data()->start();
293         }
294         else
295         {
296             // at least on plugins doesn't support pipes
297 
298             // estimated size of the wav file
299             int estimatedFileSize = 0;
300             if( item->conversionPipes.at(item->take).trunks.first().plugin->type() == "ripper" && item->fileListItem->tags )
301             {
302                 estimatedFileSize = item->fileListItem->tags->length * 44100*16*2/8/1024/1024;
303             }
304             else
305             {
306                 QFileInfo fileInfo( inputUrl.toLocalFile() );
307                 estimatedFileSize = fileInfo.size();
308                 if( config->pluginLoader()->isCodecLossless(item->fileListItem->codecName) )
309                 {
310                     estimatedFileSize *= 1.4;
311                 }
312                 else if( item->fileListItem->codecName == "midi" || item->fileListItem->codecName == "mod" )
313                 {
314                     estimatedFileSize *= 1000;
315                 }
316                 else
317                 {
318                     estimatedFileSize *= 10;
319                 }
320                 estimatedFileSize /= 1024*1024;
321             }
322             const bool useSharedMemory = config->data.advanced.useSharedMemoryForTempFiles && estimatedFileSize > 0 && estimatedFileSize < config->data.advanced.maxSizeForSharedMemoryTempFiles;
323 
324             for( int i=0; i<item->conversionPipes.at(item->take).trunks.count()-1; i++ )
325             {
326                 item->tempConvertUrls += item->generateTempUrl( "convert", config->pluginLoader()->codecExtensions(item->conversionPipes.at(item->take).trunks.at(i).codecTo).first(), useSharedMemory );
327             }
328 
329             convertNextBackend( item );
330         }
331     }
332 }
333 
convertNextBackend(ConvertItem * item)334 void Convert::convertNextBackend( ConvertItem *item )
335 {
336     if( !item )
337         return;
338 
339     const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions( item->fileListItem->conversionOptionsId );
340     if( !conversionOptions )
341         return;
342 
343     const int stepCount = item->conversionPipes.at(item->take).trunks.count() - 1;
344     const int step = ++item->conversionPipesStep;
345 
346     if( step > stepCount )
347     {
348         executeNextStep( item );
349     }
350 
351     BackendPlugin *plugin = item->conversionPipes.at(item->take).trunks.at(step).plugin;
352     if( !plugin )
353         return;
354 
355     KUrl inputUrl;
356     if( !item->tempInputUrl.toLocalFile().isEmpty() )
357         inputUrl = item->tempInputUrl;
358     else
359         inputUrl = item->inputUrl;
360 
361     const KUrl inUrl = ( step == 0 ) ? inputUrl : item->tempConvertUrls.at(step - 1);
362     const KUrl outUrl = ( step == stepCount ) ? item->outputUrl : item->tempConvertUrls.at(step);
363 
364     if( step == 0 )
365     {
366         if( plugin->type() == "ripper" )
367         {
368             item->state = ConvertItem::rip;
369             logger->log( item->logID, i18n("Ripping") );
370         }
371         else if( plugin->type() == "codec" || ( plugin->type() == "filter" && item->conversionPipes.at(item->take).trunks.at(step).codecFrom != "wav" ) )
372         {
373             item->state = ConvertItem::decode;
374             logger->log( item->logID, i18n("Decoding") );
375         }
376         else if( plugin->type() == "filter" )
377         {
378             item->state = ConvertItem::filter;
379             logger->log( item->logID, i18n("Applying filter") );
380         }
381     }
382     else if( step == stepCount )
383     {
384         if( plugin->type() == "codec" || ( plugin->type() == "filter" && item->conversionPipes.at(item->take).trunks.at(step).codecTo != "wav" ) )
385         {
386             item->state = ConvertItem::encode;
387             logger->log( item->logID, i18n("Encoding") );
388         }
389         else if( plugin->type() == "filter" )
390         {
391             item->state = ConvertItem::filter;
392             logger->log( item->logID, i18n("Applying filter") );
393         }
394     }
395     else
396     {
397         item->state = ConvertItem::filter;
398         logger->log( item->logID, i18n("Applying filter") );
399     }
400 
401     item->backendPlugin = plugin;
402     if( plugin->type() == "codec" || plugin->type() == "filter" )
403     {
404         bool useInternalReplayGain = false;
405         if( step == stepCount && item->conversionPipes.at(item->take).trunks.at(step).data.hasInternalReplayGain && item->mode & ConvertItem::replaygain )
406         {
407             foreach( const Config::CodecData& codecData, config->data.backends.codecs )
408             {
409                 if( codecData.codecName == item->conversionPipes.at(item->take).trunks.at(step).codecTo )
410                 {
411                     if( codecData.replaygain.first() == i18n("Try internal") )
412                         useInternalReplayGain = true;
413 
414                     break;
415                 }
416             }
417         }
418         if( useInternalReplayGain )
419         {
420             item->internalReplayGainUsed = true;
421         }
422         item->backendID = qobject_cast<CodecPlugin*>(plugin)->convert( inUrl, outUrl, item->conversionPipes.at(item->take).trunks.at(step).codecFrom, item->conversionPipes.at(item->take).trunks.at(step).codecTo, conversionOptions, item->fileListItem->tags, useInternalReplayGain );
423     }
424     else if( plugin->type() == "ripper" )
425     {
426         item->backendID = qobject_cast<RipperPlugin*>(plugin)->rip( item->fileListItem->device, item->fileListItem->track, item->fileListItem->tracks, outUrl );
427     }
428 
429     if( item->backendID < 100 )
430     {
431         switch( item->backendID )
432         {
433             case BackendPlugin::BackendNeedsConfiguration:
434             {
435                 logger->log( item->logID, "\t" + i18n("Conversion failed. At least one of the used backends needs to be configured properly.") );
436                 remove( item, FileListItem::BackendNeedsConfiguration );
437                 break;
438             }
439             case BackendPlugin::FeatureNotSupported:
440             {
441                 logger->log( item->logID, "\t" + i18n("Conversion failed. The preferred plugin lacks support for a necessary feature.") );
442                 executeSameStep( item );
443                 break;
444             }
445             case BackendPlugin::UnknownError:
446             {
447                 logger->log( item->logID, "\t" + i18n("Conversion failed. Unknown Error.") );
448                 executeSameStep( item );
449                 break;
450             }
451         }
452     }
453     else if( item->backendPlugin->type() == "ripper" )
454     {
455         item->fileListItem->state = FileListItem::Ripping;
456     }
457 }
458 
replaygain(ConvertItem * item)459 void Convert::replaygain( ConvertItem *item )
460 {
461     if( !item )
462         return;
463 
464     const QString albumName = item->fileListItem->tags ? item->fileListItem->tags->album : "";
465 
466     if( fileList->waitForAlbumGain(item->fileListItem) )
467     {
468         logger->log( item->logID, i18n("Skipping Replay Gain, Album Gain will be calculated later") );
469         item->state = ConvertItem::replaygain;
470 
471         if( !albumGainItems[albumName].contains(item) )
472         {
473             albumGainItems[albumName].append( item );
474             executeNextStep( item );
475         }
476         else
477         {
478             logger->log( item->logID, "\t" + i18n("No more backends left to try :(") );
479             albumGainItems[albumName].removeAll( item );
480             remove( item, FileListItem::Failed );
481         }
482         return;
483     }
484 
485     QList<ConvertItem*> albumItems;
486     if( !albumName.isEmpty() )
487         albumItems = albumGainItems[albumName];
488 
489     albumItems.append( item );
490 
491     logger->log( item->logID, i18n("Applying Replay Gain") );
492 
493     if( item->take > item->replaygainPipes.count() - 1 )
494     {
495         logger->log( item->logID, "\t" + i18n("No more backends left to try :(") );
496         remove( item, FileListItem::Failed );
497         return;
498     }
499 
500     item->state = ConvertItem::replaygain;
501     item->backendPlugin = item->replaygainPipes.at(item->take).plugin;
502 
503     KUrl::List urlList;
504     QStringList directories;
505     foreach( ConvertItem *albumItem, albumItems )
506     {
507         urlList.append( albumItem->outputUrl );
508         directories.append( albumItem->outputUrl.directory() );
509         if( albumItem != item )
510         {
511             albumItem->fileListItem->state = FileListItem::ApplyingAlbumGain;
512             fileList->updateItem( albumItem->fileListItem );
513         }
514     }
515 
516     if( item->backendPlugin->name() == "Vorbis Gain" )
517     {
518         bool waitForVorbisGainFinish = false;
519 
520         foreach( const QString directory, directories )
521         {
522             if( activeVorbisGainDirectories.contains(directory) )
523             {
524                 waitForVorbisGainFinish = true;
525                 break;
526             }
527         }
528 
529         if( waitForVorbisGainFinish )
530         {
531             logger->log( item->logID, i18n("Waiting for running Vorbis Gain process to finish") );
532             item->state = ConvertItem::wait_replaygain;
533             item->fileListItem->state = FileListItem::WaitingForAlbumGain;
534             fileList->updateItem( item->fileListItem );
535             emit finished( 0, FileListItem::Failed, false ); // send signal to FileList; trigger call of FileList::convertNextItem()
536             return;
537         }
538         else
539         {
540             activeVorbisGainDirectories.append( directories );
541         }
542     }
543 
544     item->backendID = qobject_cast<ReplayGainPlugin*>(item->backendPlugin)->apply( urlList );
545 
546     if( item->backendID < 100 )
547     {
548         switch( item->backendID )
549         {
550             case BackendPlugin::BackendNeedsConfiguration:
551             {
552                 logger->log( item->logID, "\t" + i18n("Conversion failed. At least one of the used backends needs to be configured properly.") );
553                 remove( item, FileListItem::BackendNeedsConfiguration );
554                 break;
555             }
556             case BackendPlugin::FeatureNotSupported:
557             {
558                 logger->log( item->logID, "\t" + i18n("Conversion failed. The preferred plugin lacks support for a necessary feature.") );
559                 executeSameStep( item );
560                 break;
561             }
562             case BackendPlugin::UnknownError:
563             {
564                 logger->log( item->logID, "\t" + i18n("Conversion failed. Unknown Error.") );
565                 executeSameStep( item );
566                 break;
567             }
568         }
569     }
570 
571     if( !updateTimer.isActive() )
572         updateTimer.start( ConfigUpdateDelay );
573 }
574 
writeTags(ConvertItem * item)575 void Convert::writeTags( ConvertItem *item )
576 {
577     if( !item || !item->fileListItem || !item->fileListItem->tags )
578         return;
579 
580     logger->log( item->logID, i18n("Writing tags") );
581 //     item->state = ConvertItem::write_tags;
582 //     item->fileListItem->setText( 0, i18n("Writing tags")+"... 00 %" );
583 
584     config->tagEngine()->writeTags( item->outputUrl, item->fileListItem->tags );
585 
586     KUrl inputUrl;
587     if( !item->tempInputUrl.toLocalFile().isEmpty() )
588         inputUrl = item->tempInputUrl;
589     else
590         inputUrl = item->inputUrl;
591 
592     if( !(item->fileListItem->tags->tagsRead & TagData::Covers) )
593     {
594         item->fileListItem->tags->covers = config->tagEngine()->readCovers( inputUrl );
595         item->fileListItem->tags->tagsRead = TagData::TagsRead(item->fileListItem->tags->tagsRead | TagData::Covers);
596     }
597 
598     const bool success = config->tagEngine()->writeCovers( item->outputUrl, item->fileListItem->tags->covers );
599     if( config->data.coverArt.writeCovers == 0 || ( config->data.coverArt.writeCovers == 1 && !success ) )
600     {
601         config->tagEngine()->writeCoversToDirectory( item->outputUrl.directory(), item->fileListItem->tags );
602     }
603 }
604 
605 // void Convert::executeUserScript( ConvertItem *item )
606 // {
607 //     logger->log( item->logID, i18n("Running user script") );
608 //     item->state = ConvertItem::execute_userscript;
609 //
610 //     KUrl source( item->fileListItem->options.filePathName );
611 //     KUrl destination( item->outputFilePathName );
612 //
613 //     item->fileListItem->setText( 0, i18n("Running user script")+"... 00 %" );
614 //
615 //     item->convertProcess->clearProgram();
616 //
617 //     QString userscript = locate( "data", "soundkonverter/userscript.sh" );
618 //     if( userscript == "" ) executeNextStep( item );
619 //
620 //     *(item->convertProcess) << userscript;
621 //     *(item->convertProcess) << source.path();
622 //     *(item->convertProcess) << destination.path();
623 //
624 //     logger->log( item->logID, userscript + " \"" + source.path() + "\" \"" + destination.path() + "\"" );
625 //
626 // // /*    item->convertProcess->setPriority( config->data.general.priority );
627 // //     item->convertProcess->start( KProcess::NotifyOnExit, KProcess::AllOutput );*/
628 // }
629 
executeNextStep(ConvertItem * item)630 void Convert::executeNextStep( ConvertItem *item )
631 {
632     logger->log( item->logID, "<br>" +  i18n("Executing next step") );
633 
634     item->lastTake = item->take;
635     item->take = 0;
636     item->progress = 0.0f;
637 
638     switch( item->state )
639     {
640         case ConvertItem::initial:
641         {
642             if( item->mode & ConvertItem::get )
643                 get( item );
644             else if( item->mode & ConvertItem::convert )
645                 convert( item );
646             else if( item->mode & ConvertItem::replaygain )
647                 replaygain( item );
648             else
649                 remove( item, FileListItem::Succeeded );
650             break;
651         }
652         case ConvertItem::get:
653         {
654             if( item->mode & ConvertItem::convert )
655                 convert( item );
656             else
657                 remove( item, FileListItem::Succeeded );
658             break;
659         }
660         case ConvertItem::convert:
661         case ConvertItem::encode:
662         {
663             if( item->mode & ConvertItem::replaygain )
664                 replaygain( item );
665             else
666                 remove( item, FileListItem::Succeeded );
667             break;
668         }
669         case ConvertItem::rip:
670         case ConvertItem::decode:
671         case ConvertItem::filter:
672         {
673             item->take = item->lastTake;
674             convertNextBackend( item );
675             break;
676         }
677         case ConvertItem::wait_replaygain:
678         {
679             replaygain( item );
680             break;
681         }
682         case ConvertItem::replaygain:
683         {
684             remove( item, FileListItem::Succeeded );
685             break;
686         }
687     }
688 }
689 
executeSameStep(ConvertItem * item)690 void Convert::executeSameStep( ConvertItem *item )
691 {
692     item->take++;
693     item->updateTimes();
694     item->progress = 0.0f;
695 
696     if( item->internalReplayGainUsed )
697     {
698         item->mode = ConvertItem::Mode( item->mode | ConvertItem::replaygain );
699         item->internalReplayGainUsed = false;
700     }
701 
702     switch( item->state )
703     {
704         case ConvertItem::initial:
705         {
706             break;
707         }
708         case ConvertItem::get:
709         {
710             get( item );
711             return;
712         }
713         case ConvertItem::rip:
714         case ConvertItem::convert:
715         case ConvertItem::decode:
716         case ConvertItem::filter:
717         case ConvertItem::encode:
718         {
719             // remove temp/failed files
720             foreach( const KUrl& url, item->tempConvertUrls )
721             {
722                 if( QFile::exists(url.toLocalFile()) )
723                 {
724                     QFile::remove( url.toLocalFile() );
725                 }
726             }
727             if( QFile::exists(item->outputUrl.toLocalFile()) )
728             {
729                 QFile::remove( item->outputUrl.toLocalFile() );
730             }
731             convert( item );
732             return;
733         }
734         case ConvertItem::wait_replaygain:
735         case ConvertItem::replaygain:
736         {
737             replaygain( item );
738             return;
739         }
740     }
741 
742     remove( item, FileListItem::Failed ); // shouldn't be possible
743 }
744 
kioJobProgress(KJob * job,unsigned long percent)745 void Convert::kioJobProgress( KJob *job, unsigned long percent )
746 {
747     // search the item list for our item
748     foreach( ConvertItem *item, items )
749     {
750         if( item->kioCopyJob.data() == job )
751         {
752             item->progress = (float)percent;
753         }
754     }
755 }
756 
kioJobFinished(KJob * job)757 void Convert::kioJobFinished( KJob *job )
758 {
759     // search the item list for our item
760     foreach( ConvertItem *item, items )
761     {
762         if( item->kioCopyJob.data() == job )
763         {
764             item->kioCopyJob.data()->deleteLater();
765 
766             if( job->error() == 0 ) // copy was successful
767             {
768                 float fileTime;
769                 switch( item->state )
770                 {
771                     case ConvertItem::get:
772                     {
773                         fileTime = item->getTime;
774                         break;
775                     }
776                     case ConvertItem::convert:
777                     {
778                         fileTime = 0.0f;
779                         foreach( const float t, item->convertTimes )
780                         {
781                             fileTime += t;
782                         }
783                         break;
784                     }
785                     default:
786                     {
787                         fileTime = 0.0f;
788                     }
789                 }
790                 if( item->state == ConvertItem::get )
791                 {
792                     if( !item->fileListItem->tags )
793                     {
794                         item->fileListItem->tags = config->tagEngine()->readTags( item->tempInputUrl );
795                         if( item->fileListItem->tags )
796                         {
797                             logger->log( item->logID, i18n("Read tags successfully") );
798                         }
799                         else
800                         {
801                             logger->log( item->logID, i18n("Unable to read tags") );
802                         }
803                     }
804                 }
805                 item->finishedTime += fileTime;
806                 executeNextStep( item );
807             }
808             else
809             {
810                 // remove temp/failed files
811                 KUrl url;
812                 if( item->state == ConvertItem::get )
813                 {
814                     url = item->tempInputUrl;
815                 }
816                 else if( item->state == ConvertItem::convert )
817                 {
818                     url = item->outputUrl;
819                 }
820 
821                 if( QFile::exists(url.toLocalFile()) )
822                 {
823                     QFile::remove(url.toLocalFile());
824                     logger->log( item->logID, i18nc("removing file","Removing: %1",url.toLocalFile()) );
825                 }
826                 if( QFile::exists(url.toLocalFile()+".part") )
827                 {
828                     QFile::remove(url.toLocalFile()+".part");
829                     logger->log( item->logID, i18nc("removing file","Removing: %1",url.toLocalFile()+".part") );
830                 }
831 
832                 if( job->error() == 1 )
833                 {
834                     remove( item, FileListItem::StoppedByUser );
835                 }
836                 else
837                 {
838                     logger->log( item->logID, i18n("An error occurred. Error code: %1 (%2)",job->error(),job->errorString()) );
839                     remove( item, FileListItem::Failed );
840                 }
841             }
842         }
843     }
844 }
845 
processOutput()846 void Convert::processOutput()
847 {
848     foreach( ConvertItem *item, items )
849     {
850         if( item->process.data() == QObject::sender() )
851         {
852             const QString output = item->process.data()->readAllStandardOutput().data();
853 
854             bool logOutput = true;
855             foreach( const ConversionPipeTrunk& trunk, item->conversionPipes.at(item->take).trunks )
856             {
857                 const float progress = trunk.plugin->parseOutput( output );
858 
859                 if( progress > item->progress )
860                 {
861                     item->progress = progress;
862                     logOutput = false;
863                 }
864             }
865 
866             if( logOutput && !output.simplified().isEmpty() )
867                 logger->log( item->logID, "<pre>\t<span style=\"color:#C00000\">" + output.trimmed().replace("\n","<br>\t") + "</span></pre>" );
868 
869             break;
870         }
871     }
872 }
873 
processExit(int exitCode,QProcess::ExitStatus exitStatus)874 void Convert::processExit( int exitCode, QProcess::ExitStatus exitStatus )
875 {
876     Q_UNUSED(exitStatus)
877 
878     if( QObject::sender() == 0 )
879     {
880         logger->log( 1000, QString("Error: processExit was called from a null sender. Exit code: %1").arg(exitCode) );
881         KMessageBox::error( 0, QString("Error: processExit was called from a null sender. Exit code: %1").arg(exitCode) );
882         return;
883     }
884 
885     // search the item list for our item
886     foreach( ConvertItem *item, items )
887     {
888         if( item->process.data() == QObject::sender() )
889         {
890             item->process.data()->deleteLater(); // NOTE crash discovered here - probably fixed by using deleteLater
891 
892             if( item->killed )
893             {
894                 remove( item, FileListItem::StoppedByUser );
895                 return;
896             }
897 
898             if( exitCode == 0 )
899             {
900                 float fileTime;
901                 switch( item->state )
902                 {
903                     case ConvertItem::initial:
904                     case ConvertItem::get:
905                     case ConvertItem::wait_replaygain:
906                     {
907                         fileTime = 0.0f;
908                         break;
909                     }
910                     case ConvertItem::convert:
911                     case ConvertItem::rip:
912                     case ConvertItem::decode:
913                     case ConvertItem::filter:
914                     case ConvertItem::encode:
915                     {
916                         fileTime = item->convertTimes.at(item->conversionPipesStep);
917                         break;
918                     }
919                     case ConvertItem::replaygain:
920                     {
921                         fileTime = item->replaygainTime;
922                         break;
923                     }
924                 }
925                 item->finishedTime += fileTime;
926 
927                 if( item->state == ConvertItem::rip )
928                 {
929                     item->fileListItem->state = FileListItem::Converting;
930                     emit rippingFinished( item->fileListItem->device );
931                 }
932 
933                 if( item->internalReplayGainUsed )
934                 {
935                     item->mode = ConvertItem::Mode( item->mode ^ ConvertItem::replaygain );
936                 }
937 
938                 switch( item->state )
939                 {
940                     case ConvertItem::rip:
941                     case ConvertItem::decode:
942                     case ConvertItem::filter:
943                     {
944                         convertNextBackend( item );
945                         break;
946                     }
947                     default:
948                     {
949                         executeNextStep( item );
950                     }
951                 }
952             }
953             else
954             {
955                 logger->log( item->logID, "\t" + i18n("Conversion failed. Exit code: %1",exitCode) );
956                 executeSameStep( item );
957             }
958         }
959     }
960 }
961 
pluginProcessFinished(int id,int exitCode)962 void Convert::pluginProcessFinished( int id, int exitCode )
963 {
964     if( QObject::sender() == 0 )
965     {
966         logger->log( 1000, QString("Error: pluginProcessFinished was called from a null sender. Exit code: %1").arg(exitCode) );
967         KMessageBox::error( 0, QString("Error: pluginProcessFinished was called from a null sender. Exit code: %1").arg(exitCode) );
968         return;
969     }
970 
971     foreach( ConvertItem *item, items )
972     {
973         if( item->backendPlugin && item->backendPlugin == QObject::sender() && item->backendID == id )
974         {
975             item->backendID = -1;
976 
977             if( item->backendPlugin->name() == "Vorbis Gain" )
978             {
979                 activeVorbisGainDirectories.removeAll( item->outputUrl.directory() );
980                 foreach( ConvertItem *nextItem, items )
981                 {
982                     if( nextItem->state == ConvertItem::wait_replaygain && nextItem->backendPlugin->name() == "Vorbis Gain" )
983                     {
984                         executeNextStep( nextItem );
985                         break;
986                     }
987                 }
988             }
989 
990             if( item->killed )
991             {
992                 remove( item, FileListItem::StoppedByUser );
993                 return;
994             }
995 
996             if( exitCode == 0 )
997             {
998                 float fileTime;
999                 switch( item->state )
1000                 {
1001                     case ConvertItem::initial:
1002                     case ConvertItem::get:
1003                     case ConvertItem::wait_replaygain:
1004                     {
1005                         fileTime = 0.0f;
1006                         break;
1007                     }
1008                     case ConvertItem::convert:
1009                     case ConvertItem::rip:
1010                     case ConvertItem::decode:
1011                     case ConvertItem::filter:
1012                     case ConvertItem::encode:
1013                     {
1014                         fileTime = item->convertTimes.at(item->conversionPipesStep);
1015                         break;
1016                     }
1017                     case ConvertItem::replaygain:
1018                     {
1019                         fileTime = item->replaygainTime;
1020                         break;
1021                     }
1022                 }
1023                 item->finishedTime += fileTime;
1024 
1025                 if( item->state == ConvertItem::rip )
1026                 {
1027                     item->fileListItem->state = FileListItem::Converting;
1028                     emit rippingFinished( item->fileListItem->device );
1029                 }
1030 
1031                 if( item->internalReplayGainUsed )
1032                 {
1033                     item->mode = ConvertItem::Mode( item->mode ^ ConvertItem::replaygain );
1034                 }
1035 
1036                 executeNextStep( item );
1037             }
1038             else
1039             {
1040                 logger->log( item->logID, "\t" + i18n("Conversion failed. Exit code: %1",exitCode) );
1041                 executeSameStep( item );
1042             }
1043         }
1044     }
1045 }
1046 
pluginLog(int id,const QString & message)1047 void Convert::pluginLog( int id, const QString& message )
1048 {
1049     // log all cached logs that can be logged now
1050     for( int i=0; i<pluginLogQueue.size(); i++ )
1051     {
1052         for( int j=0; j<items.size(); j++ )
1053         {
1054             if( items.at(j)->backendPlugin && items.at(j)->backendPlugin == pluginLogQueue.at(i).plugin && items.at(j)->backendID == pluginLogQueue.at(i).id )
1055             {
1056                 for( int k=0; k<pluginLogQueue.at(i).messages.size(); k++ )
1057                 {
1058                     logger->log( items.at(j)->logID, pluginLogQueue.at(i).messages.at(k) );
1059                 }
1060                 pluginLogQueue.removeAt(i);
1061                 i--;
1062                 break;
1063             }
1064         }
1065     }
1066 
1067     if( message.trimmed().isEmpty() )
1068         return;
1069 
1070     // search the matching process
1071     for( int i=0; i<items.size(); i++ )
1072     {
1073         if( items.at(i)->backendPlugin && items.at(i)->backendPlugin == QObject::sender() && items.at(i)->backendID == id )
1074         {
1075             logger->log( items.at(i)->logID, message );
1076             return;
1077         }
1078     }
1079 
1080     // if the process can't be found; add to the log queue
1081     for( int i=0; i<pluginLogQueue.size(); i++ )
1082     {
1083         if( pluginLogQueue.at(i).plugin == QObject::sender() && pluginLogQueue.at(i).id == id )
1084         {
1085             pluginLogQueue[i].messages += message;
1086             return;
1087         }
1088     }
1089 
1090     // no existing item in the log queue; create new log queue item
1091     LogQueue newLog;
1092     newLog.plugin = qobject_cast<BackendPlugin*>(QObject::sender());
1093     newLog.id = id;
1094     newLog.messages = QStringList(message);
1095     pluginLogQueue += newLog;
1096 
1097 //     logger->log( 1000, qobject_cast<BackendPlugin*>(QObject::sender())->name() + ": " + message.trimmed().replace("\n","\n\t") );
1098 }
1099 
add(FileListItem * fileListItem)1100 void Convert::add( FileListItem *fileListItem )
1101 {
1102     KUrl fileName;
1103     if( fileListItem->track >= 0 )
1104     {
1105         if( fileListItem->tags )
1106         {
1107             fileName = KUrl( i18nc("identificator for the logger","CD track %1: %2 - %3",QString().sprintf("%02i",fileListItem->tags->track),fileListItem->tags->artist,fileListItem->tags->title) );
1108         }
1109         else // shouldn't be possible
1110         {
1111             fileName = KUrl( i18nc("identificator for the logger","CD track %1",fileListItem->track) );
1112         }
1113     }
1114     else
1115     {
1116         fileName = fileListItem->url;
1117     }
1118     logger->log( 1000, i18n("Adding new item to conversion list: '%1'",fileName.pathOrUrl()) );
1119 
1120     ConvertItem *newItem = new ConvertItem( fileListItem );
1121     items.append( newItem );
1122 
1123     newItem->progressedTime.start();
1124 
1125     // register at the logger
1126     newItem->logID = logger->registerProcess( fileName.pathOrUrl() );
1127     logger->log( 1000, "\t" + i18n("Got log ID: %1",QString::number(newItem->logID)) );
1128 
1129     // TODO remove redundancy, logID is needed in the fileListItem
1130     newItem->fileListItem->logId = newItem->logID;
1131 
1132 //     logger->log( newItem->logID, "Mime Type: " + newItem->fileListItem->mimeType );
1133 //     if( newItem->fileListItem->tags ) logger->log( newItem->logID, i18n("Tags successfully read") );
1134 //     else logger->log( newItem->logID, i18n("Reading tags failed") );
1135 
1136     if( fileListItem->tags && fileListItem->tags->isEncrypted )
1137     {
1138         logger->log( newItem->logID, "<br><span style=\"color:#C00000\">" + i18n("File is encrypted, conversion not possible") + "</span>" );
1139         remove( newItem, FileListItem::Encrypted );
1140         return;
1141     }
1142 
1143     // set some variables to default values
1144     newItem->mode = (ConvertItem::Mode)0x0000;
1145     newItem->state = (ConvertItem::Mode)0x0000;
1146 
1147     newItem->inputUrl = fileListItem->url;
1148 
1149     const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions(fileListItem->conversionOptionsId);
1150     if( !conversionOptions )
1151     {
1152         logger->log( 1000, "Convert::add(...) no ConversionOptions found" );
1153         remove( newItem, FileListItem::Failed );
1154         return;
1155     }
1156 
1157     if( fileListItem->track >= 0 )
1158     {
1159         logger->log( newItem->logID, "\t" + i18n("Track number: %1, device: %2",QString::number(fileListItem->track),fileListItem->device) );
1160     }
1161 
1162     newItem->conversionPipes = config->pluginLoader()->getConversionPipes( fileListItem->codecName, conversionOptions->codecName, conversionOptions->filterOptions, conversionOptions->pluginName );
1163 
1164     logger->log( newItem->logID, "\t" + i18n("Possible conversion strategies:") );
1165     for( int i=0; i<newItem->conversionPipes.size(); i++ )
1166     {
1167         QStringList pipe_str;
1168 
1169         for( int j=0; j<newItem->conversionPipes.at(i).trunks.size(); j++ )
1170         {
1171             pipe_str += QString("%1 %2 %3 (%4)").arg(newItem->conversionPipes.at(i).trunks.at(j).codecFrom).arg(QChar(10141)).arg(newItem->conversionPipes.at(i).trunks.at(j).codecTo).arg(newItem->conversionPipes.at(i).trunks.at(j).plugin->name());
1172         }
1173 
1174         logger->log( newItem->logID, "\t\t" + pipe_str.join(", ") );
1175     }
1176 
1177     if( conversionOptions->outputDirectoryMode != OutputDirectory::Source )
1178         logger->log( newItem->logID, i18n("File system type: %1",conversionOptions->outputFilesystem) );
1179 
1180     newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::convert );
1181 
1182     if( conversionOptions->replaygain )
1183     {
1184         newItem->replaygainPipes = config->pluginLoader()->getReplayGainPipes( conversionOptions->codecName );
1185         newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::replaygain );
1186     }
1187 
1188     if( !newItem->inputUrl.isLocalFile() && fileListItem->track == -1 )
1189         newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::get );
1190 //     if( (!newItem->inputUrl.isLocalFile() && item->track == -1) || newItem->inputUrl.url().toAscii() != newItem->inputUrl.url() )
1191 //         newItem->mode = ConvertItem::Mode( newItem->mode | ConvertItem::get );
1192 
1193     newItem->updateTimes();
1194 
1195     // (visual) feedback
1196     fileListItem->state = FileListItem::Converting;
1197 
1198     // and start
1199     executeNextStep( newItem );
1200 }
1201 
remove(ConvertItem * item,FileListItem::ReturnCode returnCode)1202 void Convert::remove( ConvertItem *item, FileListItem::ReturnCode returnCode )
1203 {
1204     // TODO "remove" (re-add) the times to the progress indicator
1205     //emit uncountTime( item->getTime + item->getCorrectionTime + item->ripTime +
1206     //                  item->decodeTime + item->encodeTime + item->replaygainTime );
1207 
1208     QString exitMessage;
1209 
1210     bool waitForAlbumGain = false;
1211     const QString albumName = item->fileListItem->tags ? item->fileListItem->tags->album : "";
1212     if( !albumName.isEmpty() )
1213         waitForAlbumGain = albumGainItems[albumName].contains( item );
1214 
1215     QFileInfo outputFileInfo( item->outputUrl.toLocalFile() );
1216     float fileRatio = outputFileInfo.size();
1217     if( item->fileListItem->track > 0 )
1218     {
1219         fileRatio /= item->fileListItem->length*4*44100;
1220     }
1221     else if( !item->fileListItem->local )
1222     {
1223         QFileInfo inputFileInfo( item->tempInputUrl.toLocalFile() );
1224         fileRatio /= inputFileInfo.size();
1225     }
1226     else
1227     {
1228         QFileInfo inputFileInfo( item->inputUrl.toLocalFile() );
1229         fileRatio /= inputFileInfo.size();
1230     }
1231     const ConversionOptions *conversionOptions = config->conversionOptionsManager()->getConversionOptions( item->fileListItem->conversionOptionsId );
1232     if( fileRatio < 0.01 && outputFileInfo.size() < 100000 && returnCode != FileListItem::StoppedByUser && ( !conversionOptions || !config->pluginLoader()->isCodecInferiorQuality(conversionOptions->codecName) ) )
1233     {
1234         exitMessage = i18n("An error occurred, the output file size is less than one percent of the input file size");
1235 
1236         if( returnCode == FileListItem::Succeeded || returnCode == FileListItem::SucceededWithProblems )
1237         {
1238             logger->log( item->logID, i18n("Conversion failed, trying again. Exit code: -2 (%1)",exitMessage) );
1239             item->take = item->lastTake;
1240             logger->log( item->logID, i18n("Removing partially converted output file") );
1241             QFile::remove( item->outputUrl.toLocalFile() );
1242             executeSameStep( item );
1243             return;
1244         }
1245     }
1246 
1247     if( returnCode == FileListItem::Succeeded && item->lastTake > 0 )
1248         returnCode = FileListItem::SucceededWithProblems;
1249 
1250     switch( returnCode )
1251     {
1252         case FileListItem::Succeeded:
1253             exitMessage = i18nc("Conversion exit status","Normal exit");
1254             break;
1255         case FileListItem::SucceededWithProblems:
1256             exitMessage = i18nc("Conversion exit status","Succeeded but problems occurred");
1257             break;
1258         case FileListItem::StoppedByUser:
1259             exitMessage = i18nc("Conversion exit status","Aborted by the user");
1260             break;
1261         case FileListItem::BackendNeedsConfiguration:
1262             exitMessage = i18nc("Conversion exit status","Backend needs configuration");
1263             break;
1264         case FileListItem::DiscFull:
1265             exitMessage = i18nc("Conversion exit status","Not enough space on the output device");
1266             break;
1267         case FileListItem::CantWriteOutput:
1268             exitMessage = i18nc("Conversion exit status","Cannot write to output directory, please check permissions");
1269             break;
1270         case FileListItem::Skipped:
1271             exitMessage = i18nc("Conversion exit status","File already exists");
1272             break;
1273         case FileListItem::Encrypted:
1274             exitMessage = i18nc("Conversion exit status","File is encrypted");
1275             break;
1276         case FileListItem::Failed:
1277             exitMessage = i18nc("Conversion exit status","An error occurred");
1278             break;
1279     }
1280 
1281     if( returnCode == FileListItem::Succeeded || returnCode == FileListItem::SucceededWithProblems )
1282         writeTags( item );
1283 
1284     if( !waitForAlbumGain && !item->fileListItem->notifyCommand.isEmpty() && ( !config->data.general.waitForAlbumGain || !conversionOptions || !conversionOptions->replaygain ) )
1285     {
1286         QList<ConvertItem*> albumItems;
1287         if( !albumName.isEmpty() )
1288             albumItems = albumGainItems[albumName];
1289 
1290         albumItems.append( item );
1291 
1292         foreach( const ConvertItem *albumItem, albumItems )
1293         {
1294             QString command = albumItem->fileListItem->notifyCommand;
1295             // command.replace( "%u", albumItem->fileListItem->url );
1296             command.replace( "%i", albumItem->inputUrl.toLocalFile() );
1297             command.replace( "%o", albumItem->outputUrl.toLocalFile() );
1298             logger->log( item->logID, i18n("Executing command: \"%1\"",command) );
1299 
1300             QProcess::startDetached( command );
1301         }
1302     }
1303 
1304     // remove temp/failed files
1305     if( QFile::exists(item->tempInputUrl.toLocalFile()) )
1306     {
1307         QFile::remove(item->tempInputUrl.toLocalFile());
1308     }
1309     foreach( const KUrl& url, item->tempConvertUrls )
1310     {
1311         if( QFile::exists(url.toLocalFile()) )
1312         {
1313             QFile::remove( url.toLocalFile() );
1314         }
1315     }
1316     if( returnCode != FileListItem::Succeeded && returnCode != FileListItem::SucceededWithProblems && returnCode != FileListItem::Skipped && QFile::exists(item->outputUrl.toLocalFile()) )
1317     {
1318         logger->log( item->logID, i18n("Removing partially converted output file") );
1319         QFile::remove(item->outputUrl.toLocalFile());
1320     }
1321 
1322     usedOutputNames.remove( item->logID );
1323 
1324     logger->log( item->logID, "<br>" +  i18n("Removing file from conversion list. Exit code %1 (%2)",returnCode,exitMessage) );
1325 
1326     logger->log( item->logID, "\t" + i18n("Conversion time") + ": " + Global::prettyNumber(item->progressedTime.elapsed(),"ms") );
1327     logger->log( item->logID, "\t" + i18n("Output file size") + ": " + Global::prettyNumber(outputFileInfo.size(),"B") );
1328     logger->log( item->logID, "\t" + i18n("File size ratio") + ": " + Global::prettyNumber(fileRatio*100,"%") );
1329 
1330     emit timeFinished( item->fileListItem->length );
1331 
1332     if( item->process.data() )
1333         item->process.data()->deleteLater();
1334     if( item->kioCopyJob.data() )
1335         item->kioCopyJob.data()->deleteLater();
1336 
1337     if( !waitForAlbumGain && !albumName.isEmpty() )
1338     {
1339         QList<ConvertItem*> albumItems = albumGainItems[albumName];
1340 
1341         foreach( const ConvertItem *albumItem, albumItems )
1342         {
1343             emit finished( albumItem->fileListItem, FileListItem::Succeeded ); // send signal to FileList
1344         }
1345         qDeleteAll( albumItems );
1346 
1347         albumGainItems.remove( albumName );
1348     }
1349     emit finished( item->fileListItem, returnCode, waitForAlbumGain ); // send signal to FileList
1350     emit finishedProcess( item->logID, returnCode == FileListItem::Succeeded, waitForAlbumGain ); // send signal to Logger
1351 
1352     items.removeAll( item );
1353 
1354     if( !waitForAlbumGain )
1355         delete item;
1356 
1357     if( items.size() == 0 && albumGainItems.size() == 0 )
1358         updateTimer.stop();
1359 }
1360 
kill(FileListItem * fileListItem)1361 void Convert::kill( FileListItem *fileListItem )
1362 {
1363     for( int i=0; i<items.size(); i++ )
1364     {
1365         if( items.at(i)->fileListItem == fileListItem )
1366         {
1367             items.at(i)->killed = true;
1368 
1369             if( items.at(i)->backendID != -1 && items.at(i)->backendPlugin )
1370             {
1371                 items.at(i)->backendPlugin->kill( items.at(i)->backendID );
1372             }
1373             else if( items.at(i)->process.data() != 0 )
1374             {
1375                 items.at(i)->process.data()->kill();
1376             }
1377             else if( items.at(i)->kioCopyJob.data() != 0 )
1378             {
1379                 items.at(i)->kioCopyJob.data()->kill( KJob::EmitResult );
1380             }
1381         }
1382     }
1383 }
1384 
itemRemoved(FileListItem * fileListItem)1385 void Convert::itemRemoved( FileListItem *fileListItem )
1386 {
1387     if( !fileListItem )
1388         return;
1389 
1390     const QString albumName = fileListItem->tags ? fileListItem->tags->album : "";
1391 
1392     if( !albumName.isEmpty() )
1393     {
1394         QList<ConvertItem*> albumItems = albumGainItems[albumName];
1395 
1396         foreach( ConvertItem *albumItem, albumItems )
1397         {
1398             if( albumItem->fileListItem == fileListItem )
1399             {
1400                 albumGainItems[albumName].removeAll( albumItem );
1401                 break;
1402             }
1403         }
1404     }
1405 }
1406 
updateProgress()1407 void Convert::updateProgress()
1408 {
1409     float time = 0.0f;
1410     QString fileProgressString;
1411 
1412     // trigger flushing of the logger cache
1413     pluginLog( 0, "" );
1414 
1415     foreach( ConvertItem *item, items )
1416     {
1417         float fileProgress = 0.0f;
1418         bool logProgress = true;
1419 
1420         if( item->backendID != -1 && item->backendPlugin )
1421         {
1422             fileProgress = item->backendPlugin->progress( item->backendID );
1423         }
1424         else
1425         {
1426             fileProgress = item->progress;
1427         }
1428 
1429         if( fileProgress >= 0 )
1430         {
1431             fileProgressString = Global::prettyNumber(fileProgress,"%");
1432         }
1433         else
1434         {
1435             fileProgressString = i18nc("The conversion progress can't be determined","Unknown");
1436             fileProgress = 0; // make it a valid value so the calculations below work
1437         }
1438 
1439         float fileTime = 0.0f;
1440 
1441         switch( item->state )
1442         {
1443             case ConvertItem::initial:
1444             {
1445                 break;
1446             }
1447             case ConvertItem::get:
1448             {
1449                 fileTime = item->getTime;
1450                 item->fileListItem->setText( 0, i18n("Getting file")+"... "+fileProgressString );
1451                 break;
1452             }
1453             case ConvertItem::convert:
1454             {
1455                 fileTime = item->convertTimes.at(item->conversionPipesStep);
1456                 item->fileListItem->setText( 0, i18n("Converting")+"... "+fileProgressString );
1457                 break;
1458             }
1459             case ConvertItem::rip:
1460             {
1461                 fileTime = item->convertTimes.at(item->conversionPipesStep);
1462                 item->fileListItem->setText( 0, i18n("Ripping")+"... "+fileProgressString );
1463                 break;
1464             }
1465             case ConvertItem::decode:
1466             {
1467                 fileTime = item->convertTimes.at(item->conversionPipesStep);
1468                 item->fileListItem->setText( 0, i18n("Decoding")+"... "+fileProgressString );
1469                 break;
1470             }
1471             case ConvertItem::filter:
1472             {
1473                 fileTime = item->convertTimes.at(item->conversionPipesStep);
1474                 item->fileListItem->setText( 0, i18n("Filter")+"... "+fileProgressString );
1475                 break;
1476             }
1477             case ConvertItem::encode:
1478             {
1479                 fileTime = item->convertTimes.at(item->conversionPipesStep);
1480                 item->fileListItem->setText( 0, i18n("Encoding")+"... "+fileProgressString );
1481                 break;
1482             }
1483             case ConvertItem::wait_replaygain:
1484             {
1485                 item->fileListItem->setText( 0, i18n("Waiting for Replay Gain") );
1486                 logProgress = false;
1487                 break;
1488             }
1489             case ConvertItem::replaygain:
1490             {
1491                 const QString albumName = item->fileListItem->tags ? item->fileListItem->tags->album : "";
1492                 QList<ConvertItem*> albumItems;
1493                 if( !albumName.isEmpty() )
1494                     albumItems = albumGainItems[albumName];
1495                 if( !albumItems.contains(item) )
1496                     albumItems.append( item );
1497                 foreach( const ConvertItem *albumItem, albumItems )
1498                 {
1499                     fileTime += albumItem->replaygainTime;
1500                 }
1501                 item->fileListItem->setText( 0, i18n("Replay Gain")+"... "+fileProgressString );
1502                 break;
1503             }
1504         }
1505         time += item->finishedTime + fileProgress * fileTime / 100.0f;
1506 
1507         if( logProgress )
1508         {
1509             logger->log( item->logID, "<pre>\t<span style=\"color:#585858\">" + i18n("Progress: %1",fileProgress) + "</span></pre>" );
1510         }
1511     }
1512 
1513     emit updateTime( time );
1514 }
1515 
1516