///////////////////////////////////////////////////////////////////////////////
// LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2013 LoRd_MuldeR <MuldeR2@GMX.de>
+// Copyright (C) 2004-2015 LoRd_MuldeR <MuldeR2@GMX.de>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
+// (at your option) any later version, but always including the *additional*
+// restrictions defined in the "License.txt" file.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
#include "Thread_FileAnalyzer.h"
+//Internal
#include "Global.h"
#include "LockedFile.h"
#include "Model_AudioFile.h"
#include "Thread_FileAnalyzer_Task.h"
+#include "PlaylistImporter.h"
+//MUtils
+#include <MUtils/Global.h>
+#include <MUtils/Exception.h>
+
+//Qt
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QImage>
#include <QThreadPool>
#include <QTime>
+#include <QElapsedTimer>
+#include <QTimer>
+#include <QQueue>
+
+//Insert into QStringList *without* duplicates
+static inline void SAFE_APPEND_STRING(QStringList &list, const QString &str)
+{
+ if(!list.contains(str, Qt::CaseInsensitive))
+ {
+ list << str;
+ }
+}
////////////////////////////////////////////////////////////
// Constructor
FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
:
- m_abortFlag(false),
+ m_tasksCounterNext(0),
+ m_tasksCounterDone(0),
m_inputFiles(inputFiles),
- m_templateFile(NULL)
+ m_templateFile(NULL),
+ m_pool(NULL)
{
m_bSuccess = false;
m_bAborted = false;
+
+ m_filesAccepted = 0;
+ m_filesRejected = 0;
+ m_filesDenied = 0;
+ m_filesDummyCDDA = 0;
+ m_filesCueSheet = 0;
+
+ moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
+
+ m_timer = new QElapsedTimer;
}
FileAnalyzer::~FileAnalyzer(void)
{
- if(m_templateFile)
+ if(m_pool)
{
- QString templatePath = m_templateFile->filePath();
- LAMEXP_DELETE(m_templateFile);
- if(QFile::exists(templatePath)) QFile::remove(templatePath);
+ if(!m_pool->waitForDone(2500))
+ {
+ qWarning("There are still running tasks in the thread pool!");
+ }
}
-
- AnalyzeTask::reset();
+
+ MUTILS_DELETE(m_templateFile);
+ MUTILS_DELETE(m_pool);
+ MUTILS_DELETE(m_timer);
}
////////////////////////////////////////////////////////////
void FileAnalyzer::run()
{
- m_abortFlag = false;
-
- m_bAborted = false;
m_bSuccess = false;
- int nFiles = m_inputFiles.count();
+ m_tasksCounterNext = 0;
+ m_tasksCounterDone = 0;
+ m_completedCounter = 0;
- emit progressMaxChanged(nFiles);
- emit progressValChanged(0);
+ m_completedFiles.clear();
+ m_completedTaskIds.clear();
+ m_runningTaskIds.clear();
+
+ m_filesAccepted = 0;
+ m_filesRejected = 0;
+ m_filesDenied = 0;
+ m_filesDummyCDDA = 0;
+ m_filesCueSheet = 0;
- m_inputFiles.sort();
+ m_timer->invalidate();
+ //Create MediaInfo template file
if(!m_templateFile)
{
if(!createTemplate())
}
}
- AnalyzeTask::reset();
- QThreadPool *pool = new QThreadPool();
- QThread::msleep(333);
+ //Sort files
+ MUtils::natural_string_sort(m_inputFiles, true);
- pool->setMaxThreadCount(qBound(2, ((QThread::idealThreadCount() * 3) / 2), 12));
+ //Handle playlist files first!
+ handlePlaylistFiles();
- while(!(m_inputFiles.isEmpty() || m_bAborted))
+ const unsigned int nFiles = m_inputFiles.count();
+ if(nFiles < 1)
{
- while(!(m_inputFiles.isEmpty() || m_bAborted))
- {
- if(!AnalyzeTask::waitForFreeSlot(&m_abortFlag))
- {
- qWarning("Timeout in AnalyzeTask::waitForFreeSlot() !!!");
- }
+ qWarning("File list is empty, nothing to do!");
+ return;
+ }
- if(m_abortFlag)
- {
- MessageBeep(MB_ICONERROR);
- m_bAborted = true;
- break;
- }
-
- if(!m_bAborted)
- {
- const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
+ //Update progress
+ emit progressMaxChanged(nFiles);
+ emit progressValChanged(0);
- AnalyzeTask *task = new AnalyzeTask(currentFile, m_templateFile->filePath(), &m_abortFlag);
- connect(task, SIGNAL(fileSelected(QString)), this, SIGNAL(fileSelected(QString)), Qt::DirectConnection);
- connect(task, SIGNAL(progressValChanged(unsigned int)), this, SIGNAL(progressValChanged(unsigned int)), Qt::DirectConnection);
- connect(task, SIGNAL(progressMaxChanged(unsigned int)), this, SIGNAL(progressMaxChanged(unsigned int)), Qt::DirectConnection);
- connect(task, SIGNAL(fileAnalyzed(AudioFileModel)), this, SIGNAL(fileAnalyzed(AudioFileModel)), Qt::DirectConnection);
+ //Create thread pool
+ if(!m_pool) m_pool = new QThreadPool();
+ const int idealThreadCount = QThread::idealThreadCount();
+ if(idealThreadCount > 0)
+ {
+ m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
+ }
- pool->start(task);
+ //Start first N threads
+ QTimer::singleShot(0, this, SLOT(initializeTasks()));
- if(int count = AnalyzeTask::getAdditionalFiles(m_inputFiles))
- {
- emit progressMaxChanged(nFiles += count);
- }
- }
- }
+ //Start event processing
+ this->exec();
- //One of the Analyze tasks may have gathered additional files from a playlist!
- if(!m_bAborted)
- {
- pool->waitForDone();
- if(int count = AnalyzeTask::getAdditionalFiles(m_inputFiles))
- {
- emit progressMaxChanged(nFiles += count);
- }
- }
- }
-
- pool->waitForDone();
- LAMEXP_DELETE(pool);
+ //Wait for pending tasks to complete
+ m_pool->waitForDone();
+ //Was opertaion aborted?
if(m_bAborted)
{
qWarning("Operation cancelled by user!");
return;
}
+ //Update progress
+ emit progressValChanged(nFiles);
+
+ //Emit pending files (this should not be required though!)
+ if(!m_completedFiles.isEmpty())
+ {
+ qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
+ QList<unsigned int> keys = m_completedFiles.keys(); qSort(keys);
+ while(!keys.isEmpty())
+ {
+ emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
+ }
+ }
+
qDebug("All files added.\n");
m_bSuccess = true;
+
+ QThread::msleep(333);
}
////////////////////////////////////////////////////////////
// Privtae Functions
////////////////////////////////////////////////////////////
+bool FileAnalyzer::analyzeNextFile(void)
+{
+ if(!(m_inputFiles.isEmpty() || m_bAborted))
+ {
+ const unsigned int taskId = m_tasksCounterNext++;
+ const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
+
+ if((!m_timer->isValid()) || (m_timer->elapsed() >= 333))
+ {
+ emit fileSelected(QFileInfo(currentFile).fileName());
+ m_timer->restart();
+ }
+
+ AnalyzeTask *task = new AnalyzeTask(taskId, currentFile, m_templateFile->filePath(), &m_bAborted);
+ connect(task, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel)), Qt::QueuedConnection);
+ connect(task, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection);
+ m_runningTaskIds.insert(taskId); m_pool->start(task);
+
+ return true;
+ }
+
+ return false;
+}
+
+void FileAnalyzer::handlePlaylistFiles(void)
+{
+ QQueue<QVariant> queue;
+ QStringList importedFromPlaylist;
+
+ //Import playlist files into "hierarchical" list
+ while(!m_inputFiles.isEmpty())
+ {
+ const QString currentFile = m_inputFiles.takeFirst();
+ QStringList importedFiles;
+ if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
+ {
+ queue.enqueue(importedFiles);
+ importedFromPlaylist << importedFiles;
+ }
+ else
+ {
+ queue.enqueue(currentFile);
+ }
+ }
+
+ //Reduce temporary list
+ importedFromPlaylist.removeDuplicates();
+
+ //Now build the complete "flat" file list (files imported from playlist take precedence!)
+ while(!queue.isEmpty())
+ {
+ const QVariant current = queue.dequeue();
+ if(current.type() == QVariant::String)
+ {
+ const QString temp = current.toString();
+ if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
+ {
+ SAFE_APPEND_STRING(m_inputFiles, temp);
+ }
+ }
+ else if(current.type() == QVariant::StringList)
+ {
+ const QStringList temp = current.toStringList();
+ for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
+ {
+ SAFE_APPEND_STRING(m_inputFiles, (*iter));
+ }
+ }
+ else
+ {
+ qWarning("Encountered an unexpected variant type!");
+ }
+ }
+}
+
bool FileAnalyzer::createTemplate(void)
{
if(m_templateFile)
return true;
}
- QString templatePath = QString("%1/%2.txt").arg(lamexp_temp_folder2(), lamexp_rand_str());
+ QString templatePath = QString("%1/%2.txt").arg(MUtils::temp_folder(), MUtils::rand_str());
QFile templateFile(templatePath);
if(!templateFile.open(QIODevice::WriteOnly))
try
{
- m_templateFile = new LockedFile(templatePath);
+ m_templateFile = new LockedFile(templatePath, true);
+ }
+ catch(const std::exception &error)
+ {
+ qWarning("Failed to lock template file:\n%s\n", error.what());
+ return false;
}
catch(...)
{
}
////////////////////////////////////////////////////////////
+// Slot Functions
+////////////////////////////////////////////////////////////
+
+void FileAnalyzer::initializeTasks(void)
+{
+ for(int i = 0; i < m_pool->maxThreadCount(); i++)
+ {
+ if(!analyzeNextFile()) break;
+ }
+}
+
+void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
+{
+ m_completedTaskIds.insert(taskId);
+
+ switch(fileType)
+ {
+ case AnalyzeTask::fileTypeNormal:
+ m_filesAccepted++;
+ if(m_tasksCounterDone == taskId)
+ {
+ emit fileAnalyzed(file);
+ m_tasksCounterDone++;
+ }
+ else
+ {
+ m_completedFiles.insert(taskId, file);
+ }
+ break;
+ case AnalyzeTask::fileTypeCDDA:
+ m_filesDummyCDDA++;
+ break;
+ case AnalyzeTask::fileTypeDenied:
+ m_filesDenied++;
+ break;
+ case AnalyzeTask::fileTypeCueSheet:
+ m_filesCueSheet++;
+ break;
+ case AnalyzeTask::fileTypeUnknown:
+ m_filesRejected++;
+ break;
+ default:
+ MUTILS_THROW("Unknown file type identifier!");
+ }
+
+ //Emit all pending files
+ while(m_completedTaskIds.contains(m_tasksCounterDone))
+ {
+ if(m_completedFiles.contains(m_tasksCounterDone))
+ {
+ emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
+ }
+ m_completedTaskIds.remove(m_tasksCounterDone);
+ m_tasksCounterDone++;
+ }
+}
+
+void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
+{
+ m_runningTaskIds.remove(taskId);
+ emit progressValChanged(++m_completedCounter);
+
+ if(!analyzeNextFile())
+ {
+ if(m_runningTaskIds.empty())
+ {
+ QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////
// Public Functions
////////////////////////////////////////////////////////////
unsigned int FileAnalyzer::filesAccepted(void)
{
- return AnalyzeTask::filesAccepted();
+ return m_filesAccepted;
}
unsigned int FileAnalyzer::filesRejected(void)
{
- return AnalyzeTask::filesRejected();
+ return m_filesRejected;
}
unsigned int FileAnalyzer::filesDenied(void)
{
- return AnalyzeTask::filesDenied();
+ return m_filesDenied;
}
unsigned int FileAnalyzer::filesDummyCDDA(void)
{
- return AnalyzeTask::filesDummyCDDA();
+ return m_filesDummyCDDA;
}
unsigned int FileAnalyzer::filesCueSheet(void)
{
- return AnalyzeTask::filesCueSheet();
+ return m_filesCueSheet;
}
////////////////////////////////////////////////////////////