1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2017 LoRd_MuldeR <MuldeR2@GMX.de>
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
23 #include "Thread_FileAnalyzer.h"
27 #include "LockedFile.h"
28 #include "Model_AudioFile.h"
29 #include "Thread_FileAnalyzer_Task.h"
30 #include "PlaylistImporter.h"
33 #include <MUtils/Global.h>
34 #include <MUtils/Exception.h>
44 #include <QThreadPool>
46 #include <QElapsedTimer>
50 //Insert into QStringList *without* duplicates
51 static inline void SAFE_APPEND_STRING(QStringList
&list
, const QString
&str
)
53 if(!list
.contains(str
, Qt::CaseInsensitive
))
59 ////////////////////////////////////////////////////////////
61 ////////////////////////////////////////////////////////////
63 FileAnalyzer::FileAnalyzer(const QStringList
&inputFiles
)
65 m_tasksCounterNext(0),
66 m_tasksCounterDone(0),
67 m_inputFiles(inputFiles
),
77 moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
79 m_timer
= new QElapsedTimer
;
82 FileAnalyzer::~FileAnalyzer(void)
86 if(!m_pool
->waitForDone(2500))
88 qWarning("There are still running tasks in the thread pool!");
92 MUTILS_DELETE(m_templateFile
);
93 MUTILS_DELETE(m_pool
);
94 MUTILS_DELETE(m_timer
);
97 ////////////////////////////////////////////////////////////
99 ////////////////////////////////////////////////////////////
101 const char *FileAnalyzer::g_tags_gen
[] =
110 "Artist", "Performer",
113 "Released_Date", "Recorded_Date",
122 const char *FileAnalyzer::g_tags_aud
[] =
138 ////////////////////////////////////////////////////////////
140 ////////////////////////////////////////////////////////////
142 void FileAnalyzer::run()
144 m_bSuccess
.fetchAndStoreOrdered(0);
146 m_tasksCounterNext
= 0;
147 m_tasksCounterDone
= 0;
148 m_completedCounter
= 0;
150 m_completedFiles
.clear();
151 m_completedTaskIds
.clear();
152 m_runningTaskIds
.clear();
157 m_filesDummyCDDA
= 0;
160 m_timer
->invalidate();
162 //Create MediaInfo template file
165 if(!createTemplate())
167 qWarning("Failed to create template file!");
173 MUtils::natural_string_sort(m_inputFiles
, true);
175 //Handle playlist files first!
176 handlePlaylistFiles();
178 const unsigned int nFiles
= m_inputFiles
.count();
181 qWarning("File list is empty, nothing to do!");
186 emit
progressMaxChanged(nFiles
);
187 emit
progressValChanged(0);
190 if(!m_pool
) m_pool
= new QThreadPool();
191 const int idealThreadCount
= QThread::idealThreadCount();
192 if(idealThreadCount
> 0)
194 m_pool
->setMaxThreadCount(qBound(2, ((idealThreadCount
* 3) / 2), 12));
197 //Start first N threads
198 QTimer::singleShot(0, this, SLOT(initializeTasks()));
200 //Start event processing
203 //Wait for pending tasks to complete
204 m_pool
->waitForDone();
206 //Was opertaion aborted?
207 if(MUTILS_BOOLIFY(m_bAborted
))
209 qWarning("Operation cancelled by user!");
214 emit
progressValChanged(nFiles
);
216 //Emit pending files (this should not be required though!)
217 if(!m_completedFiles
.isEmpty())
219 qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
220 QList
<unsigned int> keys
= m_completedFiles
.keys(); qSort(keys
);
221 while(!keys
.isEmpty())
223 emit
fileAnalyzed(m_completedFiles
.take(keys
.takeFirst()));
227 qDebug("All files added.\n");
228 m_bSuccess
.fetchAndStoreOrdered(1);
229 QThread::msleep(333);
232 ////////////////////////////////////////////////////////////
234 ////////////////////////////////////////////////////////////
236 bool FileAnalyzer::analyzeNextFile(void)
238 if(!(m_inputFiles
.isEmpty() || MUTILS_BOOLIFY(m_bAborted
)))
240 const unsigned int taskId
= m_tasksCounterNext
++;
241 const QString currentFile
= QDir::fromNativeSeparators(m_inputFiles
.takeFirst());
243 if((!m_timer
->isValid()) || (m_timer
->elapsed() >= 333))
245 emit
fileSelected(QFileInfo(currentFile
).fileName());
249 AnalyzeTask
*task
= new AnalyzeTask(taskId
, currentFile
, m_templateFile
->filePath(), m_bAborted
);
250 connect(task
, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel
)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel
)), Qt::QueuedConnection
);
251 connect(task
, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection
);
252 m_runningTaskIds
.insert(taskId
); m_pool
->start(task
);
260 void FileAnalyzer::handlePlaylistFiles(void)
262 QQueue
<QVariant
> queue
;
263 QStringList importedFromPlaylist
;
265 //Import playlist files into "hierarchical" list
266 while(!m_inputFiles
.isEmpty())
268 const QString currentFile
= m_inputFiles
.takeFirst();
269 QStringList importedFiles
;
270 if(PlaylistImporter::importPlaylist(importedFiles
, currentFile
))
272 queue
.enqueue(importedFiles
);
273 importedFromPlaylist
<< importedFiles
;
277 queue
.enqueue(currentFile
);
281 //Reduce temporary list
282 importedFromPlaylist
.removeDuplicates();
284 //Now build the complete "flat" file list (files imported from playlist take precedence!)
285 while(!queue
.isEmpty())
287 const QVariant current
= queue
.dequeue();
288 if(current
.type() == QVariant::String
)
290 const QString temp
= current
.toString();
291 if(!importedFromPlaylist
.contains(temp
, Qt::CaseInsensitive
))
293 SAFE_APPEND_STRING(m_inputFiles
, temp
);
296 else if(current
.type() == QVariant::StringList
)
298 const QStringList temp
= current
.toStringList();
299 for(QStringList::ConstIterator iter
= temp
.constBegin(); iter
!= temp
.constEnd(); iter
++)
301 SAFE_APPEND_STRING(m_inputFiles
, (*iter
));
306 qWarning("Encountered an unexpected variant type!");
311 bool FileAnalyzer::createTemplate(void)
315 qWarning("Template file already exists!");
319 QString templatePath
= QString("%1/%2.txt").arg(MUtils::temp_folder(), MUtils::next_rand_str());
321 QFile
templateFile(templatePath
);
322 if(!templateFile
.open(QIODevice::WriteOnly
))
327 templateFile
.write("General;");
328 for(size_t i
= 0; g_tags_gen
[i
]; i
++)
330 templateFile
.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen
[i
]).toLatin1().constData());
332 templateFile
.write("\\n\r\n");
334 templateFile
.write("Audio;");
335 for(size_t i
= 0; g_tags_aud
[i
]; i
++)
337 templateFile
.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud
[i
]).toLatin1().constData());
339 templateFile
.write("\\n\r\n");
341 bool success
= (templateFile
.error() == QFile::NoError
);
342 templateFile
.close();
346 QFile::remove(templatePath
);
352 m_templateFile
= new LockedFile(templatePath
, true);
354 catch(const std::exception
&error
)
356 qWarning("Failed to lock template file:\n%s\n", error
.what());
361 qWarning("Failed to lock template file!");
368 ////////////////////////////////////////////////////////////
370 ////////////////////////////////////////////////////////////
372 void FileAnalyzer::initializeTasks(void)
374 for(int i
= 0; i
< m_pool
->maxThreadCount(); i
++)
376 if(!analyzeNextFile()) break;
380 void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId
, const int fileType
, const AudioFileModel
&file
)
382 m_completedTaskIds
.insert(taskId
);
386 case AnalyzeTask::fileTypeNormal
:
388 if(m_tasksCounterDone
== taskId
)
390 emit
fileAnalyzed(file
);
391 m_tasksCounterDone
++;
395 m_completedFiles
.insert(taskId
, file
);
398 case AnalyzeTask::fileTypeCDDA
:
401 case AnalyzeTask::fileTypeDenied
:
404 case AnalyzeTask::fileTypeCueSheet
:
407 case AnalyzeTask::fileTypeUnknown
:
411 MUTILS_THROW("Unknown file type identifier!");
414 //Emit all pending files
415 while(m_completedTaskIds
.contains(m_tasksCounterDone
))
417 if(m_completedFiles
.contains(m_tasksCounterDone
))
419 emit
fileAnalyzed(m_completedFiles
.take(m_tasksCounterDone
));
421 m_completedTaskIds
.remove(m_tasksCounterDone
);
422 m_tasksCounterDone
++;
426 void FileAnalyzer::taskThreadFinish(const unsigned int taskId
)
428 m_runningTaskIds
.remove(taskId
);
429 emit
progressValChanged(++m_completedCounter
);
431 if(!analyzeNextFile())
433 if(m_runningTaskIds
.empty())
435 QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
440 ////////////////////////////////////////////////////////////
442 ////////////////////////////////////////////////////////////
444 unsigned int FileAnalyzer::filesAccepted(void)
446 return m_filesAccepted
;
449 unsigned int FileAnalyzer::filesRejected(void)
451 return m_filesRejected
;
454 unsigned int FileAnalyzer::filesDenied(void)
456 return m_filesDenied
;
459 unsigned int FileAnalyzer::filesDummyCDDA(void)
461 return m_filesDummyCDDA
;
464 unsigned int FileAnalyzer::filesCueSheet(void)
466 return m_filesCueSheet
;
469 ////////////////////////////////////////////////////////////
471 ////////////////////////////////////////////////////////////