Some changes to "portable mode" detection.
[LameXP.git] / src / Thread_FileAnalyzer.cpp
blob2bc4ed4563a902d4f53d7104b2d96e5361296e0b
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2017 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, 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"
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),
68 m_templateFile(NULL),
69 m_pool(NULL)
71 m_filesAccepted = 0;
72 m_filesRejected = 0;
73 m_filesDenied = 0;
74 m_filesDummyCDDA = 0;
75 m_filesCueSheet = 0;
77 moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
79 m_timer = new QElapsedTimer;
82 FileAnalyzer::~FileAnalyzer(void)
84 if(m_pool)
86 if(!m_pool->waitForDone(2500))
88 qWarning("There are still running tasks in the thread pool!");
92 MUTILS_DELETE(m_templateFile);
93 MUTILS_DELETE(m_pool);
94 MUTILS_DELETE(m_timer);
97 ////////////////////////////////////////////////////////////
98 // Static data
99 ////////////////////////////////////////////////////////////
101 const char *FileAnalyzer::g_tags_gen[] =
103 "ID",
104 "Format",
105 "Format_Profile",
106 "Format_Version",
107 "Duration",
108 "Title", "Track",
109 "Track/Position",
110 "Artist", "Performer",
111 "Album",
112 "Genre",
113 "Released_Date", "Recorded_Date",
114 "Comment",
115 "Cover",
116 "Cover_Type",
117 "Cover_Mime",
118 "Cover_Data",
119 NULL
122 const char *FileAnalyzer::g_tags_aud[] =
124 "ID",
125 "Source",
126 "Format",
127 "Format_Profile",
128 "Format_Version",
129 "Channel(s)",
130 "SamplingRate",
131 "BitDepth",
132 "BitRate",
133 "BitRate_Mode",
134 "Encoded_Library",
135 NULL
138 ////////////////////////////////////////////////////////////
139 // Thread Main
140 ////////////////////////////////////////////////////////////
142 void FileAnalyzer::run()
144 m_bSuccess.fetchAndStoreOrdered(0);
146 m_tasksCounterNext = 0;
147 m_tasksCounterDone = 0;
148 m_completedCounter = 0;
150 m_completedFiles.clear();
151 m_completedTaskIds.clear();
152 m_runningTaskIds.clear();
154 m_filesAccepted = 0;
155 m_filesRejected = 0;
156 m_filesDenied = 0;
157 m_filesDummyCDDA = 0;
158 m_filesCueSheet = 0;
160 m_timer->invalidate();
162 //Create MediaInfo template file
163 if(!m_templateFile)
165 if(!createTemplate())
167 qWarning("Failed to create template file!");
168 return;
172 //Sort files
173 MUtils::natural_string_sort(m_inputFiles, true);
175 //Handle playlist files first!
176 handlePlaylistFiles();
178 const unsigned int nFiles = m_inputFiles.count();
179 if(nFiles < 1)
181 qWarning("File list is empty, nothing to do!");
182 return;
185 //Update progress
186 emit progressMaxChanged(nFiles);
187 emit progressValChanged(0);
189 //Create thread pool
190 if(!m_pool) m_pool = new QThreadPool();
191 const int idealThreadCount = QThread::idealThreadCount();
192 if(idealThreadCount > 0)
194 m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
197 //Start first N threads
198 QTimer::singleShot(0, this, SLOT(initializeTasks()));
200 //Start event processing
201 this->exec();
203 //Wait for pending tasks to complete
204 m_pool->waitForDone();
206 //Was opertaion aborted?
207 if(MUTILS_BOOLIFY(m_bAborted))
209 qWarning("Operation cancelled by user!");
210 return;
213 //Update progress
214 emit progressValChanged(nFiles);
216 //Emit pending files (this should not be required though!)
217 if(!m_completedFiles.isEmpty())
219 qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
220 QList<unsigned int> keys = m_completedFiles.keys(); qSort(keys);
221 while(!keys.isEmpty())
223 emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
227 qDebug("All files added.\n");
228 m_bSuccess.fetchAndStoreOrdered(1);
229 QThread::msleep(333);
232 ////////////////////////////////////////////////////////////
233 // Privtae Functions
234 ////////////////////////////////////////////////////////////
236 bool FileAnalyzer::analyzeNextFile(void)
238 if(!(m_inputFiles.isEmpty() || MUTILS_BOOLIFY(m_bAborted)))
240 const unsigned int taskId = m_tasksCounterNext++;
241 const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
243 if((!m_timer->isValid()) || (m_timer->elapsed() >= 333))
245 emit fileSelected(QFileInfo(currentFile).fileName());
246 m_timer->restart();
249 AnalyzeTask *task = new AnalyzeTask(taskId, currentFile, m_templateFile->filePath(), m_bAborted);
250 connect(task, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel)), Qt::QueuedConnection);
251 connect(task, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection);
252 m_runningTaskIds.insert(taskId); m_pool->start(task);
254 return true;
257 return false;
260 void FileAnalyzer::handlePlaylistFiles(void)
262 QQueue<QVariant> queue;
263 QStringList importedFromPlaylist;
265 //Import playlist files into "hierarchical" list
266 while(!m_inputFiles.isEmpty())
268 const QString currentFile = m_inputFiles.takeFirst();
269 QStringList importedFiles;
270 if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
272 queue.enqueue(importedFiles);
273 importedFromPlaylist << importedFiles;
275 else
277 queue.enqueue(currentFile);
281 //Reduce temporary list
282 importedFromPlaylist.removeDuplicates();
284 //Now build the complete "flat" file list (files imported from playlist take precedence!)
285 while(!queue.isEmpty())
287 const QVariant current = queue.dequeue();
288 if(current.type() == QVariant::String)
290 const QString temp = current.toString();
291 if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
293 SAFE_APPEND_STRING(m_inputFiles, temp);
296 else if(current.type() == QVariant::StringList)
298 const QStringList temp = current.toStringList();
299 for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
301 SAFE_APPEND_STRING(m_inputFiles, (*iter));
304 else
306 qWarning("Encountered an unexpected variant type!");
311 bool FileAnalyzer::createTemplate(void)
313 if(m_templateFile)
315 qWarning("Template file already exists!");
316 return true;
319 QString templatePath = QString("%1/%2.txt").arg(MUtils::temp_folder(), MUtils::next_rand_str());
321 QFile templateFile(templatePath);
322 if(!templateFile.open(QIODevice::WriteOnly))
324 return false;
327 templateFile.write("General;");
328 for(size_t i = 0; g_tags_gen[i]; i++)
330 templateFile.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen[i]).toLatin1().constData());
332 templateFile.write("\\n\r\n");
334 templateFile.write("Audio;");
335 for(size_t i = 0; g_tags_aud[i]; i++)
337 templateFile.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud[i]).toLatin1().constData());
339 templateFile.write("\\n\r\n");
341 bool success = (templateFile.error() == QFile::NoError);
342 templateFile.close();
344 if(!success)
346 QFile::remove(templatePath);
347 return false;
352 m_templateFile = new LockedFile(templatePath, true);
354 catch(const std::exception &error)
356 qWarning("Failed to lock template file:\n%s\n", error.what());
357 return false;
359 catch(...)
361 qWarning("Failed to lock template file!");
362 return false;
365 return true;
368 ////////////////////////////////////////////////////////////
369 // Slot Functions
370 ////////////////////////////////////////////////////////////
372 void FileAnalyzer::initializeTasks(void)
374 for(int i = 0; i < m_pool->maxThreadCount(); i++)
376 if(!analyzeNextFile()) break;
380 void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
382 m_completedTaskIds.insert(taskId);
384 switch(fileType)
386 case AnalyzeTask::fileTypeNormal:
387 m_filesAccepted++;
388 if(m_tasksCounterDone == taskId)
390 emit fileAnalyzed(file);
391 m_tasksCounterDone++;
393 else
395 m_completedFiles.insert(taskId, file);
397 break;
398 case AnalyzeTask::fileTypeCDDA:
399 m_filesDummyCDDA++;
400 break;
401 case AnalyzeTask::fileTypeDenied:
402 m_filesDenied++;
403 break;
404 case AnalyzeTask::fileTypeCueSheet:
405 m_filesCueSheet++;
406 break;
407 case AnalyzeTask::fileTypeUnknown:
408 m_filesRejected++;
409 break;
410 default:
411 MUTILS_THROW("Unknown file type identifier!");
414 //Emit all pending files
415 while(m_completedTaskIds.contains(m_tasksCounterDone))
417 if(m_completedFiles.contains(m_tasksCounterDone))
419 emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
421 m_completedTaskIds.remove(m_tasksCounterDone);
422 m_tasksCounterDone++;
426 void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
428 m_runningTaskIds.remove(taskId);
429 emit progressValChanged(++m_completedCounter);
431 if(!analyzeNextFile())
433 if(m_runningTaskIds.empty())
435 QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
440 ////////////////////////////////////////////////////////////
441 // Public Functions
442 ////////////////////////////////////////////////////////////
444 unsigned int FileAnalyzer::filesAccepted(void)
446 return m_filesAccepted;
449 unsigned int FileAnalyzer::filesRejected(void)
451 return m_filesRejected;
454 unsigned int FileAnalyzer::filesDenied(void)
456 return m_filesDenied;
459 unsigned int FileAnalyzer::filesDummyCDDA(void)
461 return m_filesDummyCDDA;
464 unsigned int FileAnalyzer::filesCueSheet(void)
466 return m_filesCueSheet;
469 ////////////////////////////////////////////////////////////
470 // EVENTS
471 ////////////////////////////////////////////////////////////
473 /*NONE*/