OSDN Git Service

Bump version.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer.cpp
index 81b6efd..f212122 100644 (file)
@@ -1,11 +1,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 // 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;
 }
 
 ////////////////////////////////////////////////////////////
@@ -257,27 +333,27 @@ bool FileAnalyzer::createTemplate(void)
 
 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;
 }
 
 ////////////////////////////////////////////////////////////