OSDN Git Service

Updated Opus binaries. Also added option to disable the resampling in Opus decoder...
[lamexp/LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 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.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Thread_FileAnalyzer_Task.h"
23
24 #include "Global.h"
25 #include "LockedFile.h"
26 #include "Model_AudioFile.h"
27 #include "PlaylistImporter.h"
28
29 #include <QDir>
30 #include <QFileInfo>
31 #include <QProcess>
32 #include <QDate>
33 #include <QTime>
34 #include <QDebug>
35 #include <QImage>
36 #include <QReadLocker>
37 #include <QWriteLocker>
38 #include <QThread>
39
40 #include <math.h>
41 #include <time.h>
42 #include <assert.h>
43
44 #define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
45 #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
46 #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
47
48 /* static vars */
49 QMutex AnalyzeTask::s_waitMutex;
50 QWaitCondition AnalyzeTask::s_waitCond;
51 QSet<unsigned int> AnalyzeTask::s_threadIdx_running;
52 unsigned int AnalyzeTask::s_threadIdx_next = 0;
53 QSemaphore AnalyzeTask::s_semaphore(0);
54
55 /* more static vars */
56 QReadWriteLock AnalyzeTask::s_lock;
57 unsigned int AnalyzeTask::s_filesAccepted = 0;
58 unsigned int AnalyzeTask::s_filesRejected = 0;
59 unsigned int AnalyzeTask::s_filesDenied = 0;
60 unsigned int AnalyzeTask::s_filesDummyCDDA = 0;
61 unsigned int AnalyzeTask::s_filesCueSheet = 0;
62 QStringList AnalyzeTask::s_additionalFiles;
63 QSet<QString> AnalyzeTask::s_recentlyAdded;
64
65 /*constants*/
66 const int WAITCOND_TIMEOUT = 2500;
67 const int MAX_RETRIES = 60000 / WAITCOND_TIMEOUT;
68 const int MAX_QUEUE_SLOTS = 32;
69
70 ////////////////////////////////////////////////////////////
71 // Constructor
72 ////////////////////////////////////////////////////////////
73
74 AnalyzeTask::AnalyzeTask(const QString &inputFile, const QString &templateFile, volatile bool *abortFlag)
75 :
76         m_threadIdx(makeThreadIdx()),
77         m_inputFile(inputFile),
78         m_templateFile(templateFile),
79         m_mediaInfoBin(lamexp_lookup_tool("mediainfo.exe")),
80         m_avs2wavBin(lamexp_lookup_tool("avs2wav.exe")),
81         m_abortFlag(abortFlag)
82 {
83         if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
84         {
85                 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
86         }
87 }
88
89 AnalyzeTask::~AnalyzeTask(void)
90 {
91         s_semaphore.release();
92         
93         s_waitMutex.lock();
94         s_threadIdx_running.remove(m_threadIdx);
95         s_waitMutex.unlock();
96
97         s_waitCond.wakeAll();
98 }
99
100 ////////////////////////////////////////////////////////////
101 // Thread Main
102 ////////////////////////////////////////////////////////////
103
104 void AnalyzeTask::run()
105 {
106         try
107         {
108                 run_ex();
109         }
110         catch(...)
111         {
112                 qWarning("WARNING: Caught an in exception AnalyzeTask thread!");
113         }
114
115         s_waitMutex.lock();
116         s_threadIdx_running.remove(m_threadIdx);
117         s_waitMutex.unlock();
118
119         s_waitCond.wakeAll();
120 }
121
122 void AnalyzeTask::run_ex(void)
123 {
124         int fileType = fileTypeNormal;
125         QString currentFile = QDir::fromNativeSeparators(m_inputFile);
126         qDebug("Analyzing: %s", currentFile.toUtf8().constData());
127         
128         emit fileSelected(QFileInfo(currentFile).fileName());
129         emit progressValChanged(m_threadIdx + 1);
130         
131         AudioFileModel file = analyzeFile(currentFile, &fileType);
132
133         if(*m_abortFlag)
134         {
135                 qWarning("Operation cancelled by user!");
136                 return;
137         }
138         if(fileType == fileTypeSkip)
139         {
140                 qWarning("File was recently added, skipping!");
141                 return;
142         }
143         if(fileType == fileTypeDenied)
144         {
145                 QWriteLocker lock(&s_lock);
146                 s_filesDenied++;
147                 lock.unlock();
148                 qWarning("Cannot access file for reading, skipping!");
149                 return;
150         }
151         if(fileType == fileTypeCDDA)
152         {
153                 QWriteLocker lock(&s_lock);
154                 s_filesDummyCDDA++;
155                 lock.unlock();
156                 qWarning("Dummy CDDA file detected, skipping!");
157                 return;
158         }
159
160         //Handle files with *incomplete* meida info
161         if(file.fileName().isEmpty() || file.formatContainerType().isEmpty() || file.formatAudioType().isEmpty())
162         {
163                 QStringList fileList;
164                 if(PlaylistImporter::importPlaylist(fileList, currentFile))
165                 {
166                         qDebug("Imported playlist file.");
167                         QWriteLocker lock(&s_lock);
168                         s_additionalFiles << fileList;
169                 }
170                 else if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
171                 {
172                         QWriteLocker lock(&s_lock);
173                         qWarning("Cue Sheet file detected, skipping!");
174                         s_filesCueSheet++;
175                 }
176                 else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
177                 {
178                         qDebug("Found a potential Avisynth script, investigating...");
179                         if(analyzeAvisynthFile(currentFile, file))
180                         {
181                                 QWriteLocker lock(&s_lock);
182                                 s_filesAccepted++;
183                                 s_recentlyAdded.insert(file.filePath().toLower());
184                                 lock.unlock();
185                                 waitForPreviousThreads();
186                                 emit fileAnalyzed(file);
187                         }
188                         else
189                         {
190                                 QWriteLocker lock(&s_lock);
191                                 qDebug("Rejected Avisynth file: %s", file.filePath().toUtf8().constData());
192                                 s_filesRejected++;
193                         }
194                 }
195                 else
196                 {
197                         QWriteLocker lock(&s_lock);
198                         qDebug("Rejected file of unknown type: %s", file.filePath().toUtf8().constData());
199                         s_filesRejected++;
200                 }
201                 return;
202         }
203
204         //Emit the file now!
205         QWriteLocker lock(&s_lock);
206         s_filesAccepted++;
207         s_recentlyAdded.insert(file.filePath().toLower());
208         lock.unlock();
209         waitForPreviousThreads();
210         emit fileAnalyzed(file);
211 }
212
213 ////////////////////////////////////////////////////////////
214 // Privtae Functions
215 ////////////////////////////////////////////////////////////
216
217 const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type)
218 {
219         *type = fileTypeNormal;
220         AudioFileModel audioFile(filePath);
221
222         QReadLocker readLock(&s_lock);
223         if(s_recentlyAdded.contains(filePath.toLower()))
224         {
225                 *type = fileTypeSkip;
226                 return audioFile;
227         }
228         readLock.unlock();
229
230         QFile readTest(filePath);
231         if(!readTest.open(QIODevice::ReadOnly))
232         {
233                 *type = fileTypeDenied;
234                 return audioFile;
235         }
236         if(checkFile_CDDA(readTest))
237         {
238                 *type = fileTypeCDDA;
239                 return audioFile;
240         }
241         readTest.close();
242
243         bool skipNext = false;
244         unsigned int id_val[2] = {UINT_MAX, UINT_MAX};
245         cover_t coverType = coverNone;
246         QByteArray coverData;
247
248         QStringList params;
249         params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile));
250         params << QDir::toNativeSeparators(filePath);
251         
252         QProcess process;
253         process.setProcessChannelMode(QProcess::MergedChannels);
254         process.setReadChannel(QProcess::StandardOutput);
255         process.start(m_mediaInfoBin, params);
256                 
257         if(!process.waitForStarted())
258         {
259                 qWarning("MediaInfo process failed to create!");
260                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
261                 process.kill();
262                 process.waitForFinished(-1);
263                 return audioFile;
264         }
265
266         while(process.state() != QProcess::NotRunning)
267         {
268                 if(*m_abortFlag)
269                 {
270                         process.kill();
271                         qWarning("Process was aborted on user request!");
272                         break;
273                 }
274                 
275                 if(!process.waitForReadyRead())
276                 {
277                         if(process.state() == QProcess::Running)
278                         {
279                                 qWarning("MediaInfo time out. Killing process and skipping file!");
280                                 process.kill();
281                                 process.waitForFinished(-1);
282                                 return audioFile;
283                         }
284                 }
285
286                 QByteArray data;
287
288                 while(process.canReadLine())
289                 {
290                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
291                         if(!line.isEmpty())
292                         {
293                                 //qDebug("Line:%s", line.toUtf8().constData());
294                                 
295                                 int index = line.indexOf('=');
296                                 if(index > 0)
297                                 {
298                                         QString key = line.left(index).trimmed();
299                                         QString val = line.mid(index+1).trimmed();
300                                         if(!key.isEmpty())
301                                         {
302                                                 updateInfo(audioFile, &skipNext, id_val, &coverType, &coverData, key, val);
303                                         }
304                                 }
305                         }
306                 }
307         }
308
309         if(audioFile.fileName().isEmpty())
310         {
311                 QString baseName = QFileInfo(filePath).fileName();
312                 int index = baseName.lastIndexOf(".");
313
314                 if(index >= 0)
315                 {
316                         baseName = baseName.left(index);
317                 }
318
319                 baseName = baseName.replace("_", " ").simplified();
320                 index = baseName.lastIndexOf(" - ");
321
322                 if(index >= 0)
323                 {
324                         baseName = baseName.mid(index + 3).trimmed();
325                 }
326
327                 audioFile.setFileName(baseName);
328         }
329         
330         process.waitForFinished();
331         if(process.state() != QProcess::NotRunning)
332         {
333                 process.kill();
334                 process.waitForFinished(-1);
335         }
336
337         if((coverType != coverNone) && (!coverData.isEmpty()))
338         {
339                 retrieveCover(audioFile, coverType, coverData);
340         }
341
342         if((audioFile.formatAudioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.formatAudioProfile().compare("Float", Qt::CaseInsensitive) == 0))
343         {
344                 if(audioFile.formatAudioBitdepth() == 32) audioFile.setFormatAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
345         }
346
347         return audioFile;
348 }
349
350 void AnalyzeTask::updateInfo(AudioFileModel &audioFile, bool *skipNext, unsigned int *id_val, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
351 {
352         //qWarning("'%s' -> '%s'", key.toUtf8().constData(), value.toUtf8().constData());
353         
354         /*New Stream*/
355         if(IS_KEY("Gen_ID") || IS_KEY("Aud_ID"))
356         {
357                 if(value.isEmpty())
358                 {
359                         *skipNext = false;
360                 }
361                 else
362                 {
363                         //We ignore all ID's, except for the lowest one!
364                         bool ok = false;
365                         unsigned int id = value.toUInt(&ok);
366                         if(ok)
367                         {
368                                 if(IS_KEY("Gen_ID")) { id_val[0] = qMin(id_val[0], id); *skipNext = (id > id_val[0]); }
369                                 if(IS_KEY("Aud_ID")) { id_val[1] = qMin(id_val[1], id); *skipNext = (id > id_val[1]); }
370                         }
371                         else
372                         {
373                                 *skipNext = true;
374                         }
375                 }
376                 if(*skipNext)
377                 {
378                         qWarning("Skipping info for non-primary stream!");
379                 }
380                 return;
381         }
382
383         /*Skip or empty?*/
384         if((*skipNext) || value.isEmpty())
385         {
386                 return;
387         }
388
389         /*Playlist file?*/
390         if(IS_KEY("Aud_Source"))
391         {
392                 *skipNext = true;
393                 audioFile.setFormatContainerType(QString());
394                 audioFile.setFormatAudioType(QString());
395                 qWarning("Skipping info for playlist file!");
396                 return;
397         }
398
399         /*General Section*/
400         if(IS_SEC("Gen"))
401         {
402                 if(IS_KEY("Gen_Format"))
403                 {
404                         audioFile.setFormatContainerType(value);
405                 }
406                 else if(IS_KEY("Gen_Format_Profile"))
407                 {
408                         audioFile.setFormatContainerProfile(value);
409                 }
410                 else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
411                 {
412                         audioFile.setFileName(value);
413                 }
414                 else if(IS_KEY("Gen_Duration"))
415                 {
416                         unsigned int tmp = parseDuration(value);
417                         if(tmp > 0) audioFile.setFileDuration(tmp);
418                 }
419                 else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
420                 {
421                         audioFile.setFileArtist(value);
422                 }
423                 else if(IS_KEY("Gen_Album"))
424                 {
425                         audioFile.setFileAlbum(value);
426                 }
427                 else if(IS_KEY("Gen_Genre"))
428                 {
429                         audioFile.setFileGenre(value);
430                 }
431                 else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
432                 {
433                         unsigned int tmp = parseYear(value);
434                         if(tmp > 0) audioFile.setFileYear(tmp);
435                 }
436                 else if(IS_KEY("Gen_Comment"))
437                 {
438                         audioFile.setFileComment(value);
439                 }
440                 else if(IS_KEY("Gen_Track/Position"))
441                 {
442                         bool ok = false;
443                         unsigned int tmp = value.toUInt(&ok);
444                         if(ok) audioFile.setFilePosition(tmp);
445                 }
446                 else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
447                 {
448                         if(*coverType == coverNone)
449                         {
450                                 *coverType = coverJpeg;
451                         }
452                 }
453                 else if(IS_KEY("Gen_Cover_Mime"))
454                 {
455                         QString temp = FIRST_TOK(value);
456                         if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
457                         else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
458                         else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
459                 }
460                 else if(IS_KEY("Gen_Cover_Data"))
461                 {
462                         if(!coverData->isEmpty()) coverData->clear();
463                         coverData->append(QByteArray::fromBase64(FIRST_TOK(value).toLatin1()));
464                 }
465                 else
466                 {
467                         qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
468                 }
469                 return;
470         }
471
472         /*Audio Section*/
473         if(IS_SEC("Aud"))
474         {
475
476                 if(IS_KEY("Aud_Format"))
477                 {
478                         audioFile.setFormatAudioType(value);
479                 }
480                 else if(IS_KEY("Aud_Format_Profile"))
481                 {
482                         audioFile.setFormatAudioProfile(value);
483                 }
484                 else if(IS_KEY("Aud_Format_Version"))
485                 {
486                         audioFile.setFormatAudioVersion(value);
487                 }
488                 else if(IS_KEY("Aud_Channel(s)"))
489                 {
490                         bool ok = false;
491                         unsigned int tmp = value.toUInt(&ok);
492                         if(ok) audioFile.setFormatAudioChannels(tmp);
493                 }
494                 else if(IS_KEY("Aud_SamplingRate"))
495                 {
496                         bool ok = false;
497                         unsigned int tmp = value.toUInt(&ok);
498                         if(ok) audioFile.setFormatAudioSamplerate(tmp);
499                 }
500                 else if(IS_KEY("Aud_BitDepth"))
501                 {
502                         bool ok = false;
503                         unsigned int tmp = value.toUInt(&ok);
504                         if(ok) audioFile.setFormatAudioBitdepth(tmp);
505                 }
506                 else if(IS_KEY("Aud_Duration"))
507                 {
508                         unsigned int tmp = parseDuration(value);
509                         if(tmp > 0) audioFile.setFileDuration(tmp);
510                 }
511                 else if(IS_KEY("Aud_BitRate"))
512                 {
513                         bool ok = false;
514                         unsigned int tmp = value.toUInt(&ok);
515                         if(ok) audioFile.setFormatAudioBitrate(tmp/1000);
516                 }
517                 else if(IS_KEY("Aud_BitRate_Mode"))
518                 {
519                         if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeConstant);
520                         if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.setFormatAudioBitrateMode(AudioFileModel::BitrateModeVariable);
521                 }
522                 else if(IS_KEY("Aud_Encoded_Library"))
523                 {
524                         audioFile.setFormatAudioEncodeLib(value);
525                 }
526                 else
527                 {
528                         qWarning("Unknown key '%s' with value '%s' found!", key.toUtf8().constData(), value.toUtf8().constData());
529                 }
530                 return;
531         }
532
533         /*Section not recognized*/
534         qWarning("Unknown section: %s", key.toUtf8().constData());
535 }
536
537 bool AnalyzeTask::checkFile_CDDA(QFile &file)
538 {
539         file.reset();
540         QByteArray data = file.read(128);
541         
542         int i = data.indexOf("RIFF");
543         int j = data.indexOf("CDDA");
544         int k = data.indexOf("fmt ");
545
546         return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
547 }
548
549 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
550 {
551         qDebug("Retrieving cover!");
552         QString extension;
553
554         switch(coverType)
555         {
556         case coverPng:
557                 extension = QString::fromLatin1("png");
558                 break;
559         case coverGif:
560                 extension = QString::fromLatin1("gif");
561                 break;
562         default:
563                 extension = QString::fromLatin1("jpg");
564                 break;
565         }
566         
567         if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
568         {
569                 QFile coverFile(QString("%1/%2.%3").arg(lamexp_temp_folder2(), lamexp_rand_str(), extension));
570                 if(coverFile.open(QIODevice::WriteOnly))
571                 {
572                         coverFile.write(coverData);
573                         coverFile.close();
574                         audioFile.setFileCover(coverFile.fileName(), true);
575                 }
576         }
577         else
578         {
579                 qWarning("Image data seems to be invalid :-(");
580         }
581 }
582
583 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
584 {
585         QProcess process;
586         process.setProcessChannelMode(QProcess::MergedChannels);
587         process.setReadChannel(QProcess::StandardOutput);
588         process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
589
590         if(!process.waitForStarted())
591         {
592                 qWarning("AVS2WAV process failed to create!");
593                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
594                 process.kill();
595                 process.waitForFinished(-1);
596                 return false;
597         }
598
599         bool bInfoHeaderFound = false;
600
601         while(process.state() != QProcess::NotRunning)
602         {
603                 if(*m_abortFlag)
604                 {
605                         process.kill();
606                         qWarning("Process was aborted on user request!");
607                         break;
608                 }
609                 
610                 if(!process.waitForReadyRead())
611                 {
612                         if(process.state() == QProcess::Running)
613                         {
614                                 qWarning("AVS2WAV time out. Killing process and skipping file!");
615                                 process.kill();
616                                 process.waitForFinished(-1);
617                                 return false;
618                         }
619                 }
620
621                 QByteArray data;
622
623                 while(process.canReadLine())
624                 {
625                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
626                         if(!line.isEmpty())
627                         {
628                                 int index = line.indexOf(':');
629                                 if(index > 0)
630                                 {
631                                         QString key = line.left(index).trimmed();
632                                         QString val = line.mid(index+1).trimmed();
633
634                                         if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
635                                         {
636                                                 if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
637                                                 {
638                                                         bool ok = false;
639                                                         unsigned int duration = val.toUInt(&ok);
640                                                         if(ok) info.setFileDuration(duration);
641                                                 }
642                                                 if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
643                                                 {
644                                                         bool ok = false;
645                                                         unsigned int samplerate = val.toUInt(&ok);
646                                                         if(ok) info.setFormatAudioSamplerate (samplerate);
647                                                 }
648                                                 if(key.compare("Channels", Qt::CaseInsensitive) == 0)
649                                                 {
650                                                         bool ok = false;
651                                                         unsigned int channels = val.toUInt(&ok);
652                                                         if(ok) info.setFormatAudioChannels(channels);
653                                                 }
654                                                 if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
655                                                 {
656                                                         bool ok = false;
657                                                         unsigned int bitdepth = val.toUInt(&ok);
658                                                         if(ok) info.setFormatAudioBitdepth(bitdepth);
659                                                 }                                       
660                                         }
661                                 }
662                                 else
663                                 {
664                                         if(line.contains("[Audio Info]", Qt::CaseInsensitive))
665                                         {
666                                                 info.setFormatAudioType("Avisynth");
667                                                 info.setFormatContainerType("Avisynth");
668                                                 bInfoHeaderFound = true;
669                                         }
670                                 }
671                         }
672                 }
673         }
674         
675         process.waitForFinished();
676         if(process.state() != QProcess::NotRunning)
677         {
678                 process.kill();
679                 process.waitForFinished(-1);
680         }
681
682         //Check exit code
683         switch(process.exitCode())
684         {
685         case 0:
686                 qDebug("Avisynth script was analyzed successfully.");
687                 return true;
688                 break;
689         case -5:
690                 qWarning("It appears that Avisynth is not installed on the system!");
691                 return false;
692                 break;
693         default:
694                 qWarning("Failed to open the Avisynth script, bad AVS file?");
695                 return false;
696                 break;
697         }
698 }
699
700 unsigned int AnalyzeTask::parseYear(const QString &str)
701 {
702         if(str.startsWith("UTC", Qt::CaseInsensitive))
703         {
704                 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
705                 if(date.isValid())
706                 {
707                         return date.year();
708                 }
709                 else
710                 {
711                         return 0;
712                 }
713         }
714         else
715         {
716                 bool ok = false;
717                 int year = str.toInt(&ok);
718                 if(ok && year > 0)
719                 {
720                         return year;
721                 }
722                 else
723                 {
724                         return 0;
725                 }
726         }
727 }
728
729 unsigned int AnalyzeTask::parseDuration(const QString &str)
730 {
731         bool ok = false;
732         unsigned int value = str.toUInt(&ok);
733         return ok ? (value/1000) : 0;
734 }
735
736 unsigned __int64 AnalyzeTask::makeThreadIdx(void)
737 {
738         s_waitMutex.lock();
739         unsigned int idx = s_threadIdx_next++;
740         s_threadIdx_running.insert(idx);
741         s_waitMutex.unlock();
742
743         return idx;
744 }
745
746 void AnalyzeTask::waitForPreviousThreads(void)
747 {
748         //This function will block until all threads with a *lower* index have terminated.
749         //Required to make sure that the files will be added in the "correct" order!
750
751         s_waitMutex.lock();
752         int retryCount = 0;
753         
754         forever
755         {
756                 bool bWaitFlag = false;
757                 QSet<unsigned int>::const_iterator i;
758
759                 for(i = s_threadIdx_running.begin(); i != s_threadIdx_running.end(); ++i)
760                 {
761                         if(*i < m_threadIdx) { bWaitFlag = true; break; }
762                 }
763
764                 if((!bWaitFlag) || *m_abortFlag)
765                 {
766                         s_waitMutex.unlock();
767                         return;
768                 }
769
770                 if(!s_waitCond.wait(&s_waitMutex, WAITCOND_TIMEOUT))
771                 {
772                         if(++retryCount > MAX_RETRIES)
773                         {
774                                 qWarning("AnalyzeTask::waitForPreviousThreads encountered timeout !!!");
775                                 s_threadIdx_running.clear();
776                         }
777                 }
778         }
779 }
780
781 ////////////////////////////////////////////////////////////
782 // Public Functions
783 ////////////////////////////////////////////////////////////
784
785 unsigned int AnalyzeTask::filesAccepted(void)
786 {
787         QReadLocker lock(&s_lock);
788         return s_filesAccepted;
789 }
790
791 unsigned int AnalyzeTask::filesRejected(void)
792 {
793         QReadLocker lock(&s_lock);
794         return s_filesRejected;
795 }
796
797 unsigned int AnalyzeTask::filesDenied(void)
798 {
799         QReadLocker lock(&s_lock);
800         return s_filesDenied;
801 }
802
803 unsigned int AnalyzeTask::filesDummyCDDA(void)
804 {
805         QReadLocker lock(&s_lock);
806         return s_filesDummyCDDA;
807 }
808
809 unsigned int AnalyzeTask::filesCueSheet(void)
810 {
811         QReadLocker lock(&s_lock);
812         return s_filesCueSheet;
813 }
814
815 int AnalyzeTask::getAdditionalFiles(QStringList &fileList)
816 {
817         QReadLocker readLock(&s_lock);
818         int count = s_additionalFiles.count();
819         readLock.unlock();
820
821         if(count > 0)
822         {
823                 QWriteLocker lock(&s_lock);
824                 count = s_additionalFiles.count();
825                 fileList << s_additionalFiles;
826                 s_additionalFiles.clear();
827                 return count;
828         }
829
830         return 0;
831 }
832
833 bool AnalyzeTask::waitForFreeSlot(volatile bool *abortFlag)
834 {
835         bool ret = false;
836
837         for(int i = 0; i < MAX_RETRIES; i++)
838         {
839                 ret = s_semaphore.tryAcquire(1, WAITCOND_TIMEOUT);
840                 if(ret || (*abortFlag)) break;
841         }
842
843         return ret;
844 }
845
846 void AnalyzeTask::reset(void)
847 {
848         QWriteLocker lock(&s_lock);
849         s_filesAccepted = 0;
850         s_filesRejected = 0;
851         s_filesDenied = 0;
852         s_filesDummyCDDA = 0;
853         s_filesCueSheet = 0;
854         s_additionalFiles.clear();
855         s_recentlyAdded.clear();
856         lock.unlock();
857
858         s_waitMutex.lock();
859         s_threadIdx_next = 0;
860         s_threadIdx_running.clear();
861         s_waitMutex.unlock();
862
863         int freeSlots = s_semaphore.available();
864         if(freeSlots < MAX_QUEUE_SLOTS)
865         {
866                 s_semaphore.release(MAX_QUEUE_SLOTS - freeSlots);
867         }
868 }
869
870 ////////////////////////////////////////////////////////////
871 // EVENTS
872 ////////////////////////////////////////////////////////////
873
874 /*NONE*/