Updated changelog.
[LameXP.git] / src / Thread_FileAnalyzer.cpp
blob56301c0d99f41fa35663b5c53dfa03faa05b43e2
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2023 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
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; always including the non-optional
9 // LAMEXP GNU GENERAL PUBLIC LICENSE ADDENDUM. See "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"
25 //Internal
26 #include "Global.h"
27 #include "LockedFile.h"
28 #include "Model_AudioFile.h"
29 #include "Thread_FileAnalyzer_Task.h"
30 #include "PlaylistImporter.h"
32 //MUtils
33 #include <MUtils/Global.h>
34 #include <MUtils/Exception.h>
36 //Qt
37 #include <QDir>
38 #include <QFileInfo>
39 #include <QProcess>
40 #include <QDate>
41 #include <QTime>
42 #include <QDebug>
43 #include <QImage>
44 #include <QThreadPool>
45 #include <QTime>
46 #include <QElapsedTimer>
47 #include <QTimer>
48 #include <QQueue>
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))
55 list << str;
59 ////////////////////////////////////////////////////////////
60 // Constructor
61 ////////////////////////////////////////////////////////////
63 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
65 m_tasksCounterNext(0),
66 m_tasksCounterDone(0),
67 m_inputFiles(inputFiles)
69 m_filesAccepted = 0;
70 m_filesRejected = 0;
71 m_filesDenied = 0;
72 m_filesDummyCDDA = 0;
73 m_filesCueSheet = 0;
75 moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
76 m_timer.reset(new QElapsedTimer());
79 FileAnalyzer::~FileAnalyzer(void)
81 if(!m_pool.isNull())
83 if(!m_pool->waitForDone(2500))
85 qWarning("There are still running tasks in the thread pool!");
90 ////////////////////////////////////////////////////////////
91 // Thread Main
92 ////////////////////////////////////////////////////////////
94 void FileAnalyzer::run()
96 m_bSuccess.fetchAndStoreOrdered(0);
98 m_tasksCounterNext = 0;
99 m_tasksCounterDone = 0;
100 m_completedCounter = 0;
102 m_completedFiles.clear();
103 m_completedTaskIds.clear();
104 m_runningTaskIds.clear();
106 m_filesAccepted = 0;
107 m_filesRejected = 0;
108 m_filesDenied = 0;
109 m_filesDummyCDDA = 0;
110 m_filesCueSheet = 0;
112 m_timer->invalidate();
114 //Sort files
115 MUtils::natural_string_sort(m_inputFiles, true);
117 //Handle playlist files first!
118 handlePlaylistFiles();
120 const unsigned int nFiles = m_inputFiles.count();
121 if(nFiles < 1)
123 qWarning("File list is empty, nothing to do!");
124 return;
127 //Update progress
128 emit progressMaxChanged(nFiles);
129 emit progressValChanged(0);
131 //Create the thread pool
132 if (m_pool.isNull())
134 m_pool.reset(new QThreadPool());
137 //Update thread count
138 const int idealThreadCount = QThread::idealThreadCount();
139 if(idealThreadCount > 0)
141 m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
144 //Start first N threads
145 QTimer::singleShot(0, this, SLOT(initializeTasks()));
147 //Start event processing
148 this->exec();
150 //Wait for pending tasks to complete
151 m_pool->waitForDone();
153 //Was opertaion aborted?
154 if(MUTILS_BOOLIFY(m_bAborted))
156 qWarning("Operation cancelled by user!");
157 return;
160 //Update progress
161 emit progressValChanged(nFiles);
163 //Emit pending files (this should not be required though!)
164 if(!m_completedFiles.isEmpty())
166 qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
167 QList<unsigned int> keys = m_completedFiles.keys(); qSort(keys);
168 while(!keys.isEmpty())
170 emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
174 qDebug("All files added.\n");
175 m_bSuccess.fetchAndStoreOrdered(1);
176 QThread::msleep(333);
179 ////////////////////////////////////////////////////////////
180 // Privtae Functions
181 ////////////////////////////////////////////////////////////
183 bool FileAnalyzer::analyzeNextFile(void)
185 if(!(m_inputFiles.isEmpty() || MUTILS_BOOLIFY(m_bAborted)))
187 const unsigned int taskId = m_tasksCounterNext++;
188 const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
190 if((!m_timer->isValid()) || (m_timer->elapsed() >= 333))
192 emit fileSelected(QFileInfo(currentFile).fileName());
193 m_timer->restart();
196 AnalyzeTask *task = new AnalyzeTask(taskId, currentFile, m_bAborted);
197 connect(task, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel)), Qt::QueuedConnection);
198 connect(task, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection);
199 m_runningTaskIds.insert(taskId); m_pool->start(task);
201 return true;
204 return false;
207 void FileAnalyzer::handlePlaylistFiles(void)
209 QQueue<QVariant> queue;
210 QStringList importedFromPlaylist;
212 //Import playlist files into "hierarchical" list
213 while(!m_inputFiles.isEmpty())
215 const QString currentFile = m_inputFiles.takeFirst();
216 QStringList importedFiles;
217 if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
219 queue.enqueue(importedFiles);
220 importedFromPlaylist << importedFiles;
222 else
224 queue.enqueue(currentFile);
228 //Reduce temporary list
229 importedFromPlaylist.removeDuplicates();
231 //Now build the complete "flat" file list (files imported from playlist take precedence!)
232 while(!queue.isEmpty())
234 const QVariant current = queue.dequeue();
235 if(current.type() == QVariant::String)
237 const QString temp = current.toString();
238 if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
240 SAFE_APPEND_STRING(m_inputFiles, temp);
243 else if(current.type() == QVariant::StringList)
245 const QStringList temp = current.toStringList();
246 for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
248 SAFE_APPEND_STRING(m_inputFiles, (*iter));
251 else
253 qWarning("Encountered an unexpected variant type!");
258 ////////////////////////////////////////////////////////////
259 // Slot Functions
260 ////////////////////////////////////////////////////////////
262 void FileAnalyzer::initializeTasks(void)
264 for(int i = 0; i < m_pool->maxThreadCount(); i++)
266 if(!analyzeNextFile()) break;
270 void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
272 m_completedTaskIds.insert(taskId);
274 switch(fileType)
276 case AnalyzeTask::fileTypeNormal:
277 m_filesAccepted++;
278 if(m_tasksCounterDone == taskId)
280 emit fileAnalyzed(file);
281 m_tasksCounterDone++;
283 else
285 m_completedFiles.insert(taskId, file);
287 break;
288 case AnalyzeTask::fileTypeCDDA:
289 m_filesDummyCDDA++;
290 break;
291 case AnalyzeTask::fileTypeDenied:
292 m_filesDenied++;
293 break;
294 case AnalyzeTask::fileTypeCueSheet:
295 m_filesCueSheet++;
296 break;
297 case AnalyzeTask::fileTypeUnknown:
298 m_filesRejected++;
299 break;
300 default:
301 MUTILS_THROW("Unknown file type identifier!");
304 //Emit all pending files
305 while(m_completedTaskIds.contains(m_tasksCounterDone))
307 if(m_completedFiles.contains(m_tasksCounterDone))
309 emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
311 m_completedTaskIds.remove(m_tasksCounterDone);
312 m_tasksCounterDone++;
316 void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
318 m_runningTaskIds.remove(taskId);
319 emit progressValChanged(++m_completedCounter);
321 if(!analyzeNextFile())
323 if(m_runningTaskIds.empty())
325 QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
330 ////////////////////////////////////////////////////////////
331 // Public Functions
332 ////////////////////////////////////////////////////////////
334 unsigned int FileAnalyzer::filesAccepted(void)
336 return m_filesAccepted;
339 unsigned int FileAnalyzer::filesRejected(void)
341 return m_filesRejected;
344 unsigned int FileAnalyzer::filesDenied(void)
346 return m_filesDenied;
349 unsigned int FileAnalyzer::filesDummyCDDA(void)
351 return m_filesDummyCDDA;
354 unsigned int FileAnalyzer::filesCueSheet(void)
356 return m_filesCueSheet;
359 ////////////////////////////////////////////////////////////
360 // EVENTS
361 ////////////////////////////////////////////////////////////
363 /*NONE*/