1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2014 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>
43 #include <QThreadPool>
45 #include <QElapsedTimer>
49 //Insert into QStringList *without* duplicates
50 static inline void SAFE_APPEND_STRING(QStringList &list, const QString &str)
52 if(!list.contains(str, Qt::CaseInsensitive))
58 ////////////////////////////////////////////////////////////
60 ////////////////////////////////////////////////////////////
62 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
64 m_tasksCounterNext(0),
65 m_tasksCounterDone(0),
66 m_inputFiles(inputFiles),
79 moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
81 m_timer = new QElapsedTimer;
84 FileAnalyzer::~FileAnalyzer(void)
88 if(!m_pool->waitForDone(2500))
90 qWarning("There are still running tasks in the thread pool!");
94 MUTILS_DELETE(m_templateFile);
95 MUTILS_DELETE(m_pool);
96 MUTILS_DELETE(m_timer);
99 ////////////////////////////////////////////////////////////
101 ////////////////////////////////////////////////////////////
103 const char *FileAnalyzer::g_tags_gen[] =
112 "Artist", "Performer",
115 "Released_Date", "Recorded_Date",
124 const char *FileAnalyzer::g_tags_aud[] =
140 ////////////////////////////////////////////////////////////
142 ////////////////////////////////////////////////////////////
144 void FileAnalyzer::run()
148 m_tasksCounterNext = 0;
149 m_tasksCounterDone = 0;
150 m_completedCounter = 0;
152 m_completedFiles.clear();
153 m_completedTaskIds.clear();
154 m_runningTaskIds.clear();
159 m_filesDummyCDDA = 0;
162 m_timer->invalidate();
164 //Create MediaInfo template file
167 if(!createTemplate())
169 qWarning("Failed to create template file!");
175 MUtils::natural_string_sort(m_inputFiles, true);
177 //Handle playlist files first!
178 handlePlaylistFiles();
180 const unsigned int nFiles = m_inputFiles.count();
183 qWarning("File list is empty, nothing to do!");
188 emit progressMaxChanged(nFiles);
189 emit progressValChanged(0);
192 if(!m_pool) m_pool = new QThreadPool();
193 const int idealThreadCount = QThread::idealThreadCount();
194 if(idealThreadCount > 0)
196 m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
199 //Start first N threads
200 QTimer::singleShot(0, this, SLOT(initializeTasks()));
202 //Start event processing
205 //Wait for pending tasks to complete
206 m_pool->waitForDone();
208 //Was opertaion aborted?
211 qWarning("Operation cancelled by user!");
216 emit progressValChanged(nFiles);
218 //Emit pending files (this should not be required though!)
219 if(!m_completedFiles.isEmpty())
221 qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
222 QList<unsigned int> keys = m_completedFiles.keys(); qSort(keys);
223 while(!keys.isEmpty())
225 emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
229 qDebug("All files added.\n");
232 QThread::msleep(333);
235 ////////////////////////////////////////////////////////////
237 ////////////////////////////////////////////////////////////
239 bool FileAnalyzer::analyzeNextFile(void)
241 if(!(m_inputFiles.isEmpty() || m_bAborted))
243 const unsigned int taskId = m_tasksCounterNext++;
244 const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
246 if((!m_timer->isValid()) || (m_timer->elapsed() >= 333))
248 emit fileSelected(QFileInfo(currentFile).fileName());
252 AnalyzeTask *task = new AnalyzeTask(taskId, currentFile, m_templateFile->filePath(), &m_bAborted);
253 connect(task, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel)), Qt::QueuedConnection);
254 connect(task, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection);
255 m_runningTaskIds.insert(taskId); m_pool->start(task);
263 void FileAnalyzer::handlePlaylistFiles(void)
265 QQueue<QVariant> queue;
266 QStringList importedFromPlaylist;
268 //Import playlist files into "hierarchical" list
269 while(!m_inputFiles.isEmpty())
271 const QString currentFile = m_inputFiles.takeFirst();
272 QStringList importedFiles;
273 if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
275 queue.enqueue(importedFiles);
276 importedFromPlaylist << importedFiles;
280 queue.enqueue(currentFile);
284 //Reduce temporary list
285 importedFromPlaylist.removeDuplicates();
287 //Now build the complete "flat" file list (files imported from playlist take precedence!)
288 while(!queue.isEmpty())
290 const QVariant current = queue.dequeue();
291 if(current.type() == QVariant::String)
293 const QString temp = current.toString();
294 if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
296 SAFE_APPEND_STRING(m_inputFiles, temp);
299 else if(current.type() == QVariant::StringList)
301 const QStringList temp = current.toStringList();
302 for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
304 SAFE_APPEND_STRING(m_inputFiles, (*iter));
309 qWarning("Encountered an unexpected variant type!");
314 bool FileAnalyzer::createTemplate(void)
318 qWarning("Template file already exists!");
322 QString templatePath = QString("%1/%2.txt").arg(MUtils::temp_folder(), MUtils::rand_str());
324 QFile templateFile(templatePath);
325 if(!templateFile.open(QIODevice::WriteOnly))
330 templateFile.write("General;");
331 for(size_t i = 0; g_tags_gen[i]; i++)
333 templateFile.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen[i]).toLatin1().constData());
335 templateFile.write("\\n\r\n");
337 templateFile.write("Audio;");
338 for(size_t i = 0; g_tags_aud[i]; i++)
340 templateFile.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud[i]).toLatin1().constData());
342 templateFile.write("\\n\r\n");
344 bool success = (templateFile.error() == QFile::NoError);
345 templateFile.close();
349 QFile::remove(templatePath);
355 m_templateFile = new LockedFile(templatePath, true);
357 catch(const std::exception &error)
359 qWarning("Failed to lock template file:\n%s\n", error.what());
364 qWarning("Failed to lock template file!");
371 ////////////////////////////////////////////////////////////
373 ////////////////////////////////////////////////////////////
375 void FileAnalyzer::initializeTasks(void)
377 for(int i = 0; i < m_pool->maxThreadCount(); i++)
379 if(!analyzeNextFile()) break;
383 void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
385 m_completedTaskIds.insert(taskId);
389 case AnalyzeTask::fileTypeNormal:
391 if(m_tasksCounterDone == taskId)
393 emit fileAnalyzed(file);
394 m_tasksCounterDone++;
398 m_completedFiles.insert(taskId, file);
401 case AnalyzeTask::fileTypeCDDA:
404 case AnalyzeTask::fileTypeDenied:
407 case AnalyzeTask::fileTypeCueSheet:
410 case AnalyzeTask::fileTypeUnknown:
414 THROW("Unknown file type identifier!");
417 //Emit all pending files
418 while(m_completedTaskIds.contains(m_tasksCounterDone))
420 if(m_completedFiles.contains(m_tasksCounterDone))
422 emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
424 m_completedTaskIds.remove(m_tasksCounterDone);
425 m_tasksCounterDone++;
429 void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
431 m_runningTaskIds.remove(taskId);
432 emit progressValChanged(++m_completedCounter);
434 if(!analyzeNextFile())
436 if(m_runningTaskIds.empty())
438 QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
443 ////////////////////////////////////////////////////////////
445 ////////////////////////////////////////////////////////////
447 unsigned int FileAnalyzer::filesAccepted(void)
449 return m_filesAccepted;
452 unsigned int FileAnalyzer::filesRejected(void)
454 return m_filesRejected;
457 unsigned int FileAnalyzer::filesDenied(void)
459 return m_filesDenied;
462 unsigned int FileAnalyzer::filesDummyCDDA(void)
464 return m_filesDummyCDDA;
467 unsigned int FileAnalyzer::filesCueSheet(void)
469 return m_filesCueSheet;
472 ////////////////////////////////////////////////////////////
474 ////////////////////////////////////////////////////////////