///////////////////////////////////////////////////////////////////////////////
// LameXP - Audio Encoder Front-End
-// Copyright (C) 2004-2013 LoRd_MuldeR <MuldeR2@GMX.de>
+// Copyright (C) 2004-2020 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
+// 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; always including the non-optional
+// LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "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_inputFiles(inputFiles),
- m_templateFile(NULL)
+ m_tasksCounterNext(0),
+ m_tasksCounterDone(0),
+ m_inputFiles(inputFiles)
{
- 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.reset(new QElapsedTimer());
}
FileAnalyzer::~FileAnalyzer(void)
{
- if(m_templateFile)
+ if(!m_pool.isNull())
{
- 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();
}
////////////////////////////////////////////////////////////
-// Static data
-////////////////////////////////////////////////////////////
-
-const char *FileAnalyzer::g_tags_gen[] =
-{
- "ID",
- "Format",
- "Format_Profile",
- "Format_Version",
- "Duration",
- "Title", "Track",
- "Track/Position",
- "Artist", "Performer",
- "Album",
- "Genre",
- "Released_Date", "Recorded_Date",
- "Comment",
- "Cover",
- "Cover_Type",
- "Cover_Mime",
- "Cover_Data",
- NULL
-};
-
-const char *FileAnalyzer::g_tags_aud[] =
-{
- "ID",
- "Source",
- "Format",
- "Format_Profile",
- "Format_Version",
- "Channel(s)",
- "SamplingRate",
- "BitDepth",
- "BitRate",
- "BitRate_Mode",
- "Encoded_Library",
- NULL
-};
-
-////////////////////////////////////////////////////////////
// Thread Main
////////////////////////////////////////////////////////////
void FileAnalyzer::run()
{
- m_abortFlag = false;
+ m_bSuccess.fetchAndStoreOrdered(0);
- m_bAborted = false;
- m_bSuccess = false;
+ m_tasksCounterNext = 0;
+ m_tasksCounterDone = 0;
+ m_completedCounter = 0;
- int nFiles = m_inputFiles.count();
+ m_completedFiles.clear();
+ m_completedTaskIds.clear();
+ m_runningTaskIds.clear();
- emit progressMaxChanged(nFiles);
- emit progressValChanged(0);
+ m_filesAccepted = 0;
+ m_filesRejected = 0;
+ m_filesDenied = 0;
+ m_filesDummyCDDA = 0;
+ m_filesCueSheet = 0;
+
+ m_timer->invalidate();
+
+ //Sort files
+ MUtils::natural_string_sort(m_inputFiles, true);
- lamexp_natural_string_sort(m_inputFiles, true); //.sort();
+ //Handle playlist files first!
+ handlePlaylistFiles();
- if(!m_templateFile)
+ const unsigned int nFiles = m_inputFiles.count();
+ if(nFiles < 1)
{
- if(!createTemplate())
- {
- qWarning("Failed to create template file!");
- return;
- }
+ qWarning("File list is empty, nothing to do!");
+ return;
}
- AnalyzeTask::reset();
- QThreadPool *pool = new QThreadPool();
- QThread::msleep(333);
-
- pool->setMaxThreadCount(qBound(2, ((QThread::idealThreadCount() * 3) / 2), 12));
+ //Update progress
+ emit progressMaxChanged(nFiles);
+ emit progressValChanged(0);
- while(!(m_inputFiles.isEmpty() || m_bAborted))
+ //Create the thread pool
+ if (m_pool.isNull())
{
- while(!(m_inputFiles.isEmpty() || m_bAborted))
- {
- if(!AnalyzeTask::waitForFreeSlot(&m_abortFlag))
- {
- qWarning("Timeout in AnalyzeTask::waitForFreeSlot() !!!");
- }
-
- if(m_abortFlag)
- {
- MessageBeep(MB_ICONERROR);
- m_bAborted = true;
- break;
- }
-
- if(!m_bAborted)
- {
- const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
+ m_pool.reset(new QThreadPool());
+ }
- 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);
+ //Update thread count
+ 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();
- if(m_bAborted)
+ //Was opertaion aborted?
+ if(MUTILS_BOOLIFY(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;
+ m_bSuccess.fetchAndStoreOrdered(1);
+ QThread::msleep(333);
}
////////////////////////////////////////////////////////////
// Privtae Functions
////////////////////////////////////////////////////////////
-bool FileAnalyzer::createTemplate(void)
+bool FileAnalyzer::analyzeNextFile(void)
{
- if(m_templateFile)
+ if(!(m_inputFiles.isEmpty() || MUTILS_BOOLIFY(m_bAborted)))
{
- qWarning("Template file already exists!");
+ 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_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;
}
-
- QString templatePath = QString("%1/%2.txt").arg(lamexp_temp_folder2(), lamexp_rand_str());
- QFile templateFile(templatePath);
- if(!templateFile.open(QIODevice::WriteOnly))
+ return false;
+}
+
+void FileAnalyzer::handlePlaylistFiles(void)
+{
+ QQueue<QVariant> queue;
+ QStringList importedFromPlaylist;
+
+ //Import playlist files into "hierarchical" list
+ while(!m_inputFiles.isEmpty())
{
- return false;
+ const QString currentFile = m_inputFiles.takeFirst();
+ QStringList importedFiles;
+ if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
+ {
+ queue.enqueue(importedFiles);
+ importedFromPlaylist << importedFiles;
+ }
+ else
+ {
+ queue.enqueue(currentFile);
+ }
}
- templateFile.write("General;");
- for(size_t i = 0; g_tags_gen[i]; i++)
+ //Reduce temporary list
+ importedFromPlaylist.removeDuplicates();
+
+ //Now build the complete "flat" file list (files imported from playlist take precedence!)
+ while(!queue.isEmpty())
{
- templateFile.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen[i]).toLatin1().constData());
+ 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!");
+ }
}
- templateFile.write("\\n\r\n");
+}
+
+////////////////////////////////////////////////////////////
+// Slot Functions
+////////////////////////////////////////////////////////////
- templateFile.write("Audio;");
- for(size_t i = 0; g_tags_aud[i]; i++)
+void FileAnalyzer::initializeTasks(void)
+{
+ for(int i = 0; i < m_pool->maxThreadCount(); i++)
{
- templateFile.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud[i]).toLatin1().constData());
+ if(!analyzeNextFile()) break;
}
- templateFile.write("\\n\r\n");
+}
- bool success = (templateFile.error() == QFile::NoError);
- templateFile.close();
-
- if(!success)
+void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
+{
+ m_completedTaskIds.insert(taskId);
+
+ switch(fileType)
{
- QFile::remove(templatePath);
- return false;
+ 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!");
}
- try
+ //Emit all pending files
+ while(m_completedTaskIds.contains(m_tasksCounterDone))
{
- m_templateFile = new LockedFile(templatePath);
+ if(m_completedFiles.contains(m_tasksCounterDone))
+ {
+ emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
+ }
+ m_completedTaskIds.remove(m_tasksCounterDone);
+ m_tasksCounterDone++;
}
- catch(...)
+}
+
+void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
+{
+ m_runningTaskIds.remove(taskId);
+ emit progressValChanged(++m_completedCounter);
+
+ if(!analyzeNextFile())
{
- qWarning("Failed to lock template file!");
- return false;
+ if(m_runningTaskIds.empty())
+ {
+ QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
+ }
}
-
- return true;
}
////////////////////////////////////////////////////////////
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;
}
////////////////////////////////////////////////////////////