OSDN Git Service

Updated changelog.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer.cpp
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!
10 //
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.
15 //
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.
19 //
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
22
23 #include "Thread_FileAnalyzer.h"
24
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"
31
32 //MUtils
33 #include <MUtils/Global.h>
34 #include <MUtils/Exception.h>
35
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>
49
50 //Insert into QStringList *without* duplicates
51 static inline void SAFE_APPEND_STRING(QStringList &list, const QString &str)
52 {
53         if(!list.contains(str, Qt::CaseInsensitive))
54         {
55                 list << str;
56         }
57 }
58
59 ////////////////////////////////////////////////////////////
60 // Constructor
61 ////////////////////////////////////////////////////////////
62
63 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
64 :
65         m_tasksCounterNext(0),
66         m_tasksCounterDone(0),
67         m_inputFiles(inputFiles)
68 {
69         m_filesAccepted = 0;
70         m_filesRejected = 0;
71         m_filesDenied = 0;
72         m_filesDummyCDDA = 0;
73         m_filesCueSheet = 0;
74
75         moveToThread(this); /*makes sure queued slots are executed in the proper thread context*/
76         m_timer.reset(new QElapsedTimer());
77 }
78
79 FileAnalyzer::~FileAnalyzer(void)
80 {
81         if(!m_pool.isNull())
82         {
83                 if(!m_pool->waitForDone(2500))
84                 {
85                         qWarning("There are still running tasks in the thread pool!");
86                 }
87         }
88 }
89
90 ////////////////////////////////////////////////////////////
91 // Thread Main
92 ////////////////////////////////////////////////////////////
93
94 void FileAnalyzer::run()
95 {
96         m_bSuccess.fetchAndStoreOrdered(0);
97
98         m_tasksCounterNext = 0;
99         m_tasksCounterDone = 0;
100         m_completedCounter = 0;
101
102         m_completedFiles.clear();
103         m_completedTaskIds.clear();
104         m_runningTaskIds.clear();
105
106         m_filesAccepted = 0;
107         m_filesRejected = 0;
108         m_filesDenied = 0;
109         m_filesDummyCDDA = 0;
110         m_filesCueSheet = 0;
111
112         m_timer->invalidate();
113
114         //Sort files
115         MUtils::natural_string_sort(m_inputFiles, true);
116
117         //Handle playlist files first!
118         handlePlaylistFiles();
119
120         const unsigned int nFiles = m_inputFiles.count();
121         if(nFiles < 1)
122         {
123                 qWarning("File list is empty, nothing to do!");
124                 return;
125         }
126
127         //Update progress
128         emit progressMaxChanged(nFiles);
129         emit progressValChanged(0);
130
131         //Create the thread pool
132         if (m_pool.isNull())
133         {
134                 m_pool.reset(new QThreadPool());
135         }
136
137         //Update thread count
138         const int idealThreadCount = QThread::idealThreadCount();
139         if(idealThreadCount > 0)
140         {
141                 m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
142         }
143
144         //Start first N threads
145         QTimer::singleShot(0, this, SLOT(initializeTasks()));
146
147         //Start event processing
148         this->exec();
149
150         //Wait for pending tasks to complete
151         m_pool->waitForDone();
152
153         //Was opertaion aborted?
154         if(MUTILS_BOOLIFY(m_bAborted))
155         {
156                 qWarning("Operation cancelled by user!");
157                 return;
158         }
159         
160         //Update progress
161         emit progressValChanged(nFiles);
162
163         //Emit pending files (this should not be required though!)
164         if(!m_completedFiles.isEmpty())
165         {
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())
169                 {
170                         emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
171                 }
172         }
173
174         qDebug("All files added.\n");
175         m_bSuccess.fetchAndStoreOrdered(1);
176         QThread::msleep(333);
177 }
178
179 ////////////////////////////////////////////////////////////
180 // Privtae Functions
181 ////////////////////////////////////////////////////////////
182
183 bool FileAnalyzer::analyzeNextFile(void)
184 {
185         if(!(m_inputFiles.isEmpty() || MUTILS_BOOLIFY(m_bAborted)))
186         {
187                 const unsigned int taskId = m_tasksCounterNext++;
188                 const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
189
190                 if((!m_timer->isValid()) || (m_timer->elapsed() >= 333))
191                 {
192                         emit fileSelected(QFileInfo(currentFile).fileName());
193                         m_timer->restart();
194                 }
195         
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);
200
201                 return true;
202         }
203
204         return false;
205 }
206
207 void FileAnalyzer::handlePlaylistFiles(void)
208 {
209         QQueue<QVariant> queue;
210         QStringList importedFromPlaylist;
211         
212         //Import playlist files into "hierarchical" list
213         while(!m_inputFiles.isEmpty())
214         {
215                 const QString currentFile = m_inputFiles.takeFirst();
216                 QStringList importedFiles;
217                 if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
218                 {
219                         queue.enqueue(importedFiles);
220                         importedFromPlaylist << importedFiles;
221                 }
222                 else
223                 {
224                         queue.enqueue(currentFile);
225                 }
226         }
227
228         //Reduce temporary list
229         importedFromPlaylist.removeDuplicates();
230
231         //Now build the complete "flat" file list (files imported from playlist take precedence!)
232         while(!queue.isEmpty())
233         {
234                 const QVariant current = queue.dequeue();
235                 if(current.type() == QVariant::String)
236                 {
237                         const QString temp = current.toString();
238                         if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
239                         {
240                                 SAFE_APPEND_STRING(m_inputFiles, temp);
241                         }
242                 }
243                 else if(current.type() == QVariant::StringList)
244                 {
245                         const QStringList temp = current.toStringList();
246                         for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
247                         {
248                                 SAFE_APPEND_STRING(m_inputFiles, (*iter));
249                         }
250                 }
251                 else
252                 {
253                         qWarning("Encountered an unexpected variant type!");
254                 }
255         }
256 }
257
258 ////////////////////////////////////////////////////////////
259 // Slot Functions
260 ////////////////////////////////////////////////////////////
261
262 void FileAnalyzer::initializeTasks(void)
263 {
264         for(int i = 0; i < m_pool->maxThreadCount(); i++)
265         {
266                 if(!analyzeNextFile()) break;
267         }
268 }
269
270 void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
271 {
272         m_completedTaskIds.insert(taskId);
273
274         switch(fileType)
275         {
276         case AnalyzeTask::fileTypeNormal:
277                 m_filesAccepted++;
278                 if(m_tasksCounterDone == taskId)
279                 {
280                         emit fileAnalyzed(file);
281                         m_tasksCounterDone++;
282                 }
283                 else
284                 {
285                         m_completedFiles.insert(taskId, file);
286                 }
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!");
302         }
303
304         //Emit all pending files
305         while(m_completedTaskIds.contains(m_tasksCounterDone))
306         {
307                 if(m_completedFiles.contains(m_tasksCounterDone))
308                 {
309                         emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
310                 }
311                 m_completedTaskIds.remove(m_tasksCounterDone);
312                 m_tasksCounterDone++;
313         }
314 }
315
316 void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
317 {
318         m_runningTaskIds.remove(taskId);
319         emit progressValChanged(++m_completedCounter);
320
321         if(!analyzeNextFile())
322         {
323                 if(m_runningTaskIds.empty())
324                 {
325                         QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
326                 }
327         }
328 }
329
330 ////////////////////////////////////////////////////////////
331 // Public Functions
332 ////////////////////////////////////////////////////////////
333
334 unsigned int FileAnalyzer::filesAccepted(void)
335 {
336         return m_filesAccepted;
337 }
338
339 unsigned int FileAnalyzer::filesRejected(void)
340 {
341         return m_filesRejected;
342 }
343
344 unsigned int FileAnalyzer::filesDenied(void)
345 {
346         return m_filesDenied;
347 }
348
349 unsigned int FileAnalyzer::filesDummyCDDA(void)
350 {
351         return m_filesDummyCDDA;
352 }
353
354 unsigned int FileAnalyzer::filesCueSheet(void)
355 {
356         return m_filesCueSheet;
357 }
358
359 ////////////////////////////////////////////////////////////
360 // EVENTS
361 ////////////////////////////////////////////////////////////
362
363 /*NONE*/