OSDN Git Service

Happy new year 2014!
[lamexp/LameXP.git] / src / Thread_FileAnalyzer.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2014 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.
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 #include "Global.h"
26 #include "LockedFile.h"
27 #include "Model_AudioFile.h"
28 #include "Thread_FileAnalyzer_Task.h"
29 #include "PlaylistImporter.h"
30
31 #include <QDir>
32 #include <QFileInfo>
33 #include <QProcess>
34 #include <QDate>
35 #include <QTime>
36 #include <QDebug>
37 #include <QImage>
38 #include <QThreadPool>
39 #include <QTime>
40 #include <QElapsedTimer>
41 #include <QTimer>
42 #include <QQueue>
43
44 //Insert into QStringList *without* duplicates
45 static inline void SAFE_APPEND_STRING(QStringList &list, const QString &str)
46 {
47         if(!list.contains(str, Qt::CaseInsensitive))
48         {
49                 list << str;
50         }
51 }
52
53 ////////////////////////////////////////////////////////////
54 // Constructor
55 ////////////////////////////////////////////////////////////
56
57 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
58 :
59         m_tasksCounterNext(0),
60         m_tasksCounterDone(0),
61         m_inputFiles(inputFiles),
62         m_templateFile(NULL),
63         m_pool(NULL)
64 {
65         m_bSuccess = false;
66         m_bAborted = false;
67
68         m_filesAccepted = 0;
69         m_filesRejected = 0;
70         m_filesDenied = 0;
71         m_filesDummyCDDA = 0;
72         m_filesCueSheet = 0;
73
74         m_timer = new QElapsedTimer;
75 }
76
77 FileAnalyzer::~FileAnalyzer(void)
78 {
79         if(!m_pool->waitForDone(2500))
80         {
81                 qWarning("There are still running tasks in the thread pool!");
82         }
83
84         LAMEXP_DELETE(m_templateFile);
85         LAMEXP_DELETE(m_pool);
86         LAMEXP_DELETE(m_timer);
87 }
88
89 ////////////////////////////////////////////////////////////
90 // Static data
91 ////////////////////////////////////////////////////////////
92
93 const char *FileAnalyzer::g_tags_gen[] =
94 {
95         "ID",
96         "Format",
97         "Format_Profile",
98         "Format_Version",
99         "Duration",
100         "Title", "Track",
101         "Track/Position",
102         "Artist", "Performer",
103         "Album",
104         "Genre",
105         "Released_Date", "Recorded_Date",
106         "Comment",
107         "Cover",
108         "Cover_Type",
109         "Cover_Mime",
110         "Cover_Data",
111         NULL
112 };
113
114 const char *FileAnalyzer::g_tags_aud[] =
115 {
116         "ID",
117         "Source",
118         "Format",
119         "Format_Profile",
120         "Format_Version",
121         "Channel(s)",
122         "SamplingRate",
123         "BitDepth",
124         "BitRate",
125         "BitRate_Mode",
126         "Encoded_Library",
127         NULL
128 };
129
130 ////////////////////////////////////////////////////////////
131 // Thread Main
132 ////////////////////////////////////////////////////////////
133
134 void FileAnalyzer::run()
135 {
136         m_bSuccess = false;
137
138         m_tasksCounterNext = 0;
139         m_tasksCounterDone = 0;
140         m_completedCounter = 0;
141
142         m_completedFiles.clear();
143         m_completedTaskIds.clear();
144         m_runningTaskIds.clear();
145
146         m_filesAccepted = 0;
147         m_filesRejected = 0;
148         m_filesDenied = 0;
149         m_filesDummyCDDA = 0;
150         m_filesCueSheet = 0;
151
152         m_timer->invalidate();
153
154         //Update progress
155         //emit progressMaxChanged(m_inputFiles.count());
156         //emit progressValChanged(0);
157
158         //Create MediaInfo template file
159         if(!m_templateFile)
160         {
161                 if(!createTemplate())
162                 {
163                         qWarning("Failed to create template file!");
164                         return;
165                 }
166         }
167
168         //Sort files
169         lamexp_natural_string_sort(m_inputFiles, true);
170
171         //Handle playlist files first!
172         handlePlaylistFiles();
173
174         const unsigned int nFiles = m_inputFiles.count();
175         if(nFiles < 1)
176         {
177                 qWarning("File list is empty, nothing to do!");
178                 return;
179         }
180
181         //Update progress
182         emit progressMaxChanged(nFiles);
183         emit progressValChanged(0);
184
185         //Create thread pool
186         if(!m_pool) m_pool = new QThreadPool();
187         const int idealThreadCount = QThread::idealThreadCount();
188         if(idealThreadCount > 0)
189         {
190                 m_pool->setMaxThreadCount(qBound(2, ((idealThreadCount * 3) / 2), 12));
191         }
192
193         //Start first N threads
194         QTimer::singleShot(0, this, SLOT(initializeTasks()));
195
196         //Start event processing
197         this->exec();
198
199         //Wait for pending tasks to complete
200         m_pool->waitForDone();
201
202         //Was opertaion aborted?
203         if(m_bAborted)
204         {
205                 qWarning("Operation cancelled by user!");
206                 return;
207         }
208         
209         //Update progress
210         emit progressValChanged(nFiles);
211
212         //Emit pending files (this should not be required though!)
213         if(!m_completedFiles.isEmpty())
214         {
215                 qWarning("FileAnalyzer: Pending file information found after last thread terminated!");
216                 QList<unsigned int> keys = m_completedFiles.keys(); qSort(keys);
217                 while(!keys.isEmpty())
218                 {
219                         emit fileAnalyzed(m_completedFiles.take(keys.takeFirst()));
220                 }
221         }
222
223         qDebug("All files added.\n");
224         m_bSuccess = true;
225
226         QThread::msleep(333);
227 }
228
229 ////////////////////////////////////////////////////////////
230 // Privtae Functions
231 ////////////////////////////////////////////////////////////
232
233 bool FileAnalyzer::analyzeNextFile(void)
234 {
235         if(!(m_inputFiles.isEmpty() || m_bAborted))
236         {
237                 const unsigned int taskId = m_tasksCounterNext++;
238                 const QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
239
240                 if((!m_timer->isValid()) || (m_timer->elapsed() >= 250))
241                 {
242                         emit fileSelected(QFileInfo(currentFile).fileName());
243                         m_timer->restart();
244                 }
245         
246                 AnalyzeTask *task = new AnalyzeTask(taskId, currentFile, m_templateFile->filePath(), &m_bAborted);
247                 connect(task, SIGNAL(fileAnalyzed(const unsigned int, const int, AudioFileModel)), this, SLOT(taskFileAnalyzed(unsigned int, const int, AudioFileModel)), Qt::QueuedConnection);
248                 connect(task, SIGNAL(taskCompleted(const unsigned int)), this, SLOT(taskThreadFinish(const unsigned int)), Qt::QueuedConnection);
249                 m_runningTaskIds.insert(taskId); m_pool->start(task);
250
251                 return true;
252         }
253
254         return false;
255 }
256
257 void FileAnalyzer::handlePlaylistFiles(void)
258 {
259         QQueue<QVariant> queue;
260         QStringList importedFromPlaylist;
261         
262         //Import playlist files into "hierarchical" list
263         while(!m_inputFiles.isEmpty())
264         {
265                 const QString currentFile = m_inputFiles.takeFirst();
266                 QStringList importedFiles;
267                 if(PlaylistImporter::importPlaylist(importedFiles, currentFile))
268                 {
269                         queue.enqueue(importedFiles);
270                         importedFromPlaylist << importedFiles;
271                 }
272                 else
273                 {
274                         queue.enqueue(currentFile);
275                 }
276         }
277
278         //Reduce temporary list
279         importedFromPlaylist.removeDuplicates();
280
281         //Now build the complete "flat" file list (files imported from playlist take precedence!)
282         while(!queue.isEmpty())
283         {
284                 const QVariant current = queue.dequeue();
285                 if(current.type() == QVariant::String)
286                 {
287                         const QString temp = current.toString();
288                         if(!importedFromPlaylist.contains(temp, Qt::CaseInsensitive))
289                         {
290                                 SAFE_APPEND_STRING(m_inputFiles, temp);
291                         }
292                 }
293                 else if(current.type() == QVariant::StringList)
294                 {
295                         const QStringList temp = current.toStringList();
296                         for(QStringList::ConstIterator iter = temp.constBegin(); iter != temp.constEnd(); iter++)
297                         {
298                                 SAFE_APPEND_STRING(m_inputFiles, (*iter));
299                         }
300                 }
301                 else
302                 {
303                         qWarning("Encountered an unexpected variant type!");
304                 }
305         }
306 }
307
308 bool FileAnalyzer::createTemplate(void)
309 {
310         if(m_templateFile)
311         {
312                 qWarning("Template file already exists!");
313                 return true;
314         }
315         
316         QString templatePath = QString("%1/%2.txt").arg(lamexp_temp_folder2(), lamexp_rand_str());
317
318         QFile templateFile(templatePath);
319         if(!templateFile.open(QIODevice::WriteOnly))
320         {
321                 return false;
322         }
323
324         templateFile.write("General;");
325         for(size_t i = 0; g_tags_gen[i]; i++)
326         {
327                 templateFile.write(QString("Gen_%1=%%1%\\n").arg(g_tags_gen[i]).toLatin1().constData());
328         }
329         templateFile.write("\\n\r\n");
330
331         templateFile.write("Audio;");
332         for(size_t i = 0; g_tags_aud[i]; i++)
333         {
334                 templateFile.write(QString("Aud_%1=%%1%\\n").arg(g_tags_aud[i]).toLatin1().constData());
335         }
336         templateFile.write("\\n\r\n");
337
338         bool success = (templateFile.error() == QFile::NoError);
339         templateFile.close();
340         
341         if(!success)
342         {
343                 QFile::remove(templatePath);
344                 return false;
345         }
346
347         try
348         {
349                 m_templateFile = new LockedFile(templatePath, true);
350         }
351         catch(const std::exception &error)
352         {
353                 qWarning("Failed to lock template file:\n%s\n", error.what());
354                 return false;
355         }
356         catch(...)
357         {
358                 qWarning("Failed to lock template file!");
359                 return false;
360         }
361
362         return true;
363 }
364
365 ////////////////////////////////////////////////////////////
366 // Slot Functions
367 ////////////////////////////////////////////////////////////
368
369 void FileAnalyzer::initializeTasks(void)
370 {
371         for(int i = 0; i < m_pool->maxThreadCount(); i++)
372         {
373                 if(!analyzeNextFile()) break;
374         }
375 }
376
377 void FileAnalyzer::taskFileAnalyzed(const unsigned int taskId, const int fileType, const AudioFileModel &file)
378 {
379         m_completedTaskIds.insert(taskId);
380
381         switch(fileType)
382         {
383         case AnalyzeTask::fileTypeNormal:
384                 m_filesAccepted++;
385                 if(m_tasksCounterDone == taskId)
386                 {
387                         emit fileAnalyzed(file);
388                         m_tasksCounterDone++;
389                 }
390                 else
391                 {
392                         m_completedFiles.insert(taskId, file);
393                 }
394                 break;
395         case AnalyzeTask::fileTypeCDDA:
396                 m_filesDummyCDDA++;
397                 break;
398         case AnalyzeTask::fileTypeDenied:
399                 m_filesDenied++;
400                 break;
401         case AnalyzeTask::fileTypeCueSheet:
402                 m_filesCueSheet++;
403                 break;
404         case AnalyzeTask::fileTypeUnknown:
405                 m_filesRejected++;
406                 break;
407         default:
408                 THROW("Unknown file type identifier!");
409         }
410
411         //Emit all pending files
412         while(m_completedTaskIds.contains(m_tasksCounterDone))
413         {
414                 if(m_completedFiles.contains(m_tasksCounterDone))
415                 {
416                         emit fileAnalyzed(m_completedFiles.take(m_tasksCounterDone));
417                 }
418                 m_completedTaskIds.remove(m_tasksCounterDone);
419                 m_tasksCounterDone++;
420         }
421 }
422
423 void FileAnalyzer::taskThreadFinish(const unsigned int taskId)
424 {
425         m_runningTaskIds.remove(taskId);
426         emit progressValChanged(++m_completedCounter);
427
428         if(!analyzeNextFile())
429         {
430                 if(m_runningTaskIds.empty())
431                 {
432                         QTimer::singleShot(0, this, SLOT(quit())); //Stop event processing, if all threads have completed!
433                 }
434         }
435 }
436
437 ////////////////////////////////////////////////////////////
438 // Public Functions
439 ////////////////////////////////////////////////////////////
440
441 unsigned int FileAnalyzer::filesAccepted(void)
442 {
443         return m_filesAccepted;
444 }
445
446 unsigned int FileAnalyzer::filesRejected(void)
447 {
448         return m_filesRejected;
449 }
450
451 unsigned int FileAnalyzer::filesDenied(void)
452 {
453         return m_filesDenied;
454 }
455
456 unsigned int FileAnalyzer::filesDummyCDDA(void)
457 {
458         return m_filesDummyCDDA;
459 }
460
461 unsigned int FileAnalyzer::filesCueSheet(void)
462 {
463         return m_filesCueSheet;
464 }
465
466 ////////////////////////////////////////////////////////////
467 // EVENTS
468 ////////////////////////////////////////////////////////////
469
470 /*NONE*/