OSDN Git Service

Updated changelog.
[lamexp/LameXP.git] / src / Thread_Initialization.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_Initialization.h"
24
25 //Internal
26 #define LAMEXP_INC_TOOLS 1
27 #include "Tools.h"
28 #include "LockedFile.h"
29 #include "FileHash.h"
30 #include "Tool_Abstract.h"
31
32 //MUtils
33 #include <MUtils/Global.h>
34 #include <MUtils/OSSupport.h>
35 #include <MUtils/Translation.h>
36 #include <MUtils/Exception.h>
37
38 //Qt
39 #include <QFileInfo>
40 #include <QCoreApplication>
41 #include <QProcess>
42 #include <QMap>
43 #include <QDir>
44 #include <QResource>
45 #include <QTextStream>
46 #include <QRunnable>
47 #include <QThreadPool>
48 #include <QMutex>
49 #include <QQueue>
50 #include <QElapsedTimer>
51 #include <QVector>
52
53 /* enable custom tools? */
54 static const bool ENABLE_CUSTOM_TOOLS = true;
55
56 /* helper macros */
57 #define PRINT_CPU_TYPE(X) case X: qDebug("Selected CPU is: " #X)
58 #define MAKE_REGEXP(STR) (((STR) && ((STR)[0])) ? QRegExp((STR)) : QRegExp())
59
60 /* constants */
61 static const double g_allowedExtractDelay = 24.0;
62 static const size_t BUFF_SIZE = 512;
63
64 /* number of CPU cores -> number of threads */
65 static unsigned int cores2threads(const unsigned int cores)
66 {
67         static const size_t LUT_LEN = 4;
68         
69         static const struct
70         {
71                 const unsigned int upperBound;
72                 const double coeffs[4];
73         }
74         LUT[LUT_LEN] =
75         {
76                 {  4, { -0.052695810565,  0.158087431694, 4.982841530055, -1.088233151184 } },
77                 {  8, {  0.042431693989, -0.983442622951, 9.548961748634, -7.176393442623 } },
78                 { 12, { -0.006277322404,  0.185573770492, 0.196830601093, 17.762622950820 } },
79                 { 32, {  0.000673497268, -0.064655737705, 3.199584699454,  5.751606557377 } }
80         };
81
82         size_t index = 0;
83         while((cores > LUT[index].upperBound) && (index < (LUT_LEN-1))) index++;
84
85         const double x = qBound(1.0, double(cores), double(LUT[LUT_LEN-1].upperBound));
86         const double y = (LUT[index].coeffs[0] * pow(x, 3.0)) + (LUT[index].coeffs[1] * pow(x, 2.0)) + (LUT[index].coeffs[2] * x) + LUT[index].coeffs[3];
87
88         return qRound(abs(y));
89 }
90
91 /* create regular expression list*/
92 static QList<QRegExp> createRegExpList(const char *const *const regExpList)
93 {
94         QList<QRegExp> result;
95         for (size_t i = 0; regExpList[i]; ++i)
96         {
97                 result << MAKE_REGEXP(regExpList[i]);
98         }
99         return result;
100 }
101
102 /* print selected CPU type*/
103 #define _CPU_FRIENDLY_NAME(X,Y) case X: return #X " (" Y ")";
104 static inline const char *cpuTypeFriendlyName(const unsigned int &cpuSupport)
105 {
106         switch (cpuSupport)
107         {
108                 _CPU_FRIENDLY_NAME(CPU_TYPE_X86_GEN, "i686")
109                 _CPU_FRIENDLY_NAME(CPU_TYPE_X86_SSE, "i686 + SSE2")
110                 _CPU_FRIENDLY_NAME(CPU_TYPE_X86_AVX, "i686 + AVX2")
111                 _CPU_FRIENDLY_NAME(CPU_TYPE_X64_SSE, "x86_64")
112                 _CPU_FRIENDLY_NAME(CPU_TYPE_X64_AVX, "x86_64 + AVX2")
113         default:
114                 MUTILS_THROW("CPU support undefined!");
115         }
116 }
117
118 ////////////////////////////////////////////////////////////
119 // BaseTask class
120 ////////////////////////////////////////////////////////////
121
122 class BaseTask : public QRunnable
123 {
124 public:
125         BaseTask(void)
126         {
127         }
128
129         ~BaseTask(void)
130         {
131         }
132
133         static void clearFlags(void)
134         {
135                 s_exception.fetchAndStoreOrdered(0);
136                 s_errMsg[0] = char(0);
137         }
138
139         static bool getExcept(void)
140         {
141                 return MUTILS_BOOLIFY(s_exception);
142         }
143
144         static bool getErrMsg(char *buffer, const size_t buffSize)
145         {
146                 if(s_errMsg[0])
147                 {
148                         strncpy_s(buffer, buffSize, s_errMsg, _TRUNCATE);
149                         return true;
150                 }
151                 return false;
152         }
153
154 protected:
155         virtual void taskMain(void) = 0;
156
157         void run(void)
158         {
159                 try
160                 {
161                         if(!getExcept()) taskMain();
162                 }
163                 catch(const std::exception &e)
164                 {
165                         if(!s_exception.fetchAndAddOrdered(1))
166                         {
167                                 strncpy_s(s_errMsg, BUFF_SIZE, e.what(), _TRUNCATE);
168                         }
169                         qWarning("BaseTask exception error:\n%s\n\n", e.what());
170                 }
171                 catch(...)
172                 {
173                         if (!s_exception.fetchAndAddOrdered(1))
174                         {
175                                 strncpy_s(s_errMsg, BUFF_SIZE, "Unknown exception error!", _TRUNCATE);
176                         }
177                         qWarning("BaseTask encountered an unknown exception!");
178                 }
179         }
180
181         static QAtomicInt s_exception;
182         static char s_errMsg[BUFF_SIZE];
183 };
184
185 char BaseTask::s_errMsg[BUFF_SIZE] = {'\0'};
186 QAtomicInt BaseTask::s_exception(0);
187
188 ////////////////////////////////////////////////////////////
189 // ExtractorTask class
190 ////////////////////////////////////////////////////////////
191
192 class ExtractorTask : public BaseTask
193 {
194 public:
195         ExtractorTask(QResource *const toolResource, const QDir &appDir, const QString &toolName, const QByteArray &toolHash, const unsigned int toolVersion, const QString &toolTag)
196         :
197                 m_appDir(appDir),
198                 m_tempPath(MUtils::temp_folder()),
199                 m_toolName(toolName),
200                 m_toolHash(toolHash),
201                 m_toolVersion(toolVersion),
202                 m_toolTag(toolTag),
203                 m_toolResource(toolResource)
204         {
205                 /* Nothing to do */
206         }
207
208         ~ExtractorTask(void)
209         {
210         }
211
212         static bool getCustom(void)
213         {
214                 return MUTILS_BOOLIFY(s_custom);
215         }
216
217         static void clearFlags(void)
218         {
219                 BaseTask::clearFlags();
220                 s_custom.fetchAndStoreOrdered(0);
221         }
222
223 protected:
224         void taskMain(void)
225         {
226                 QScopedPointer<LockedFile> lockedFile;
227                 unsigned int version = m_toolVersion;
228
229                 const QFileInfo toolFileInfo(m_toolName);
230                 const QString   toolShrtName = QString("%1.%2").arg(toolFileInfo.baseName().toLower(), toolFileInfo.suffix().toLower());
231
232                 //Try to load a "custom" tool first
233                 if(ENABLE_CUSTOM_TOOLS)
234                 {
235                         const QFileInfo customTool(QString("%1/tools/%2/%3").arg(m_appDir.canonicalPath(), QString::number(lamexp_version_build()), toolShrtName));
236                         if(customTool.exists() && customTool.isFile())
237                         {
238                                 qDebug("Setting up file: %s <- %s", toolShrtName.toLatin1().constData(), m_appDir.relativeFilePath(customTool.canonicalFilePath()).toLatin1().constData());
239                                 try
240                                 {
241                                         lockedFile.reset(new LockedFile(customTool.canonicalFilePath()));
242                                         version = UINT_MAX; s_custom.ref();
243                                 }
244                                 catch(std::runtime_error&)
245                                 {
246                                         lockedFile.reset();
247                                 }
248                         }
249                 }
250
251                 //Try to load the tool from the "cache" next
252                 if(lockedFile.isNull())
253                 {
254                         const QFileInfo chachedTool(QString("%1/cache/%2").arg(m_appDir.canonicalPath(), toolFileInfo.fileName()));
255                         if(chachedTool.exists() && chachedTool.isFile())
256                         {
257                                 qDebug("Validating file: %s <- %s", toolShrtName.toLatin1().constData(), m_appDir.relativeFilePath(chachedTool.canonicalFilePath()).toLatin1().constData());
258                                 try
259                                 {
260                                         lockedFile.reset(new LockedFile(chachedTool.canonicalFilePath(), m_toolHash));
261                                 }
262                                 catch(std::runtime_error&)
263                                 {
264                                         lockedFile.reset();
265                                 }
266                         }
267                 }
268
269                 //If still not initialized, extract tool now!
270                 if(lockedFile.isNull())
271                 {
272                         qDebug("Extracting file: %s -> %s", m_toolName.toLatin1().constData(), toolShrtName.toLatin1().constData());
273                         lockedFile.reset(new LockedFile(m_toolResource.data(), QString("%1/lxp_%2").arg(m_tempPath, toolShrtName), m_toolHash));
274                 }
275
276                 //Register tool
277                 lamexp_tools_register(toolShrtName, lockedFile.take(), version, m_toolTag);
278         }
279
280 private:
281         static QAtomicInt         s_custom;
282         QScopedPointer<QResource> m_toolResource;
283         const QDir                m_appDir;
284         const QString             m_tempPath;
285         const QString             m_toolName;
286         const QByteArray          m_toolHash;
287         const unsigned int        m_toolVersion;
288         const QString             m_toolTag;
289 };
290
291 QAtomicInt ExtractorTask::s_custom = false;
292
293 ////////////////////////////////////////////////////////////
294 // InitAacEncTask class
295 ////////////////////////////////////////////////////////////
296
297 class InitAacEncTask : public BaseTask
298 {
299 public:
300         InitAacEncTask(const aac_encoder_t *const encoder_info)
301         :
302                 m_encoder_info(encoder_info)
303         {
304         }
305
306         ~InitAacEncTask(void)
307         {
308         }
309
310 protected:
311         void taskMain(void)
312         {
313                 QRegExp regExpVer = MAKE_REGEXP(m_encoder_info->regExpVer);
314                 QRegExp regExpSig = MAKE_REGEXP(m_encoder_info->regExpSig);
315                 initAacEncImpl
316                 (
317                         m_encoder_info->toolName,
318                         m_encoder_info->fileNames,
319                         m_encoder_info->checkArgs ? (QStringList() << QString::fromLatin1(m_encoder_info->checkArgs)) : QStringList(),
320                         m_encoder_info->toolMinVersion,
321                         m_encoder_info->verDigits,
322                         m_encoder_info->verShift,
323                         m_encoder_info->verStr,
324                         regExpVer,
325                         regExpSig,
326                         m_encoder_info->regExpLib[0] ? createRegExpList(m_encoder_info->regExpLib) : QList<QRegExp>()
327                 );
328         }
329
330         static void initAacEncImpl(const char *const toolName, const char *const fileNames[], const QStringList &checkArgs, const quint32 &toolMinVersion, const quint32 &verDigits, const quint32 &verShift, const char *const verStr, QRegExp &regExpVer, QRegExp &regExpSig, const QList<QRegExp> &regExpLib = QList<QRegExp>());
331
332 private:
333         const aac_encoder_t *const m_encoder_info;
334 };
335
336 ////////////////////////////////////////////////////////////
337 // Constructor
338 ////////////////////////////////////////////////////////////
339
340 InitializationThread::InitializationThread(const MUtils::CPUFetaures::cpu_info_t &cpuFeatures)
341 :
342         m_bSuccess(false),
343         m_slowIndicator(false)
344 {
345
346         memcpy(&m_cpuFeatures, &cpuFeatures, sizeof(MUtils::CPUFetaures::cpu_info_t));
347 }
348
349 ////////////////////////////////////////////////////////////
350 // Thread Main
351 ////////////////////////////////////////////////////////////
352
353 void InitializationThread::run(void)
354 {
355         try
356         {
357                 doInit();
358         }
359         catch(const std::exception &error)
360         {
361                 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
362                 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
363         }
364         catch(...)
365         {
366                 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
367                 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
368         }
369 }
370
371 double InitializationThread::doInit(const size_t threadCount)
372 {
373         m_bSuccess = false;
374         delay();
375
376         //CPU type selection
377         unsigned int cpuSupport = CPU_TYPE_X86_GEN;
378         if (m_cpuFeatures.x64 || ((m_cpuFeatures.features & MUtils::CPUFetaures::FLAG_SSE) && (m_cpuFeatures.features & MUtils::CPUFetaures::FLAG_SSE2)))
379         {
380                 cpuSupport = m_cpuFeatures.x64 ? CPU_TYPE_X64_SSE : CPU_TYPE_X86_SSE;
381                 if ((m_cpuFeatures.features & MUtils::CPUFetaures::FLAG_AVX) && (m_cpuFeatures.features & MUtils::CPUFetaures::FLAG_AVX2))
382                 {
383                         //Note: AVX is supported on Windows 7 SP1 or later
384                         const MUtils::OS::Version::os_version_t &osVersion = MUtils::OS::os_version();
385                         if ((osVersion >= MUtils::OS::Version::WINDOWS_WIN80) || ((osVersion >= MUtils::OS::Version::WINDOWS_WIN70) && (osVersion.versionSPack >= 1)))
386                         {
387                                 cpuSupport = m_cpuFeatures.x64 ? CPU_TYPE_X64_AVX : CPU_TYPE_X86_AVX;
388                         }
389                 }
390         }
391
392         //Hack to disable x64 on Wine, as x64 binaries won't run under Wine (tested with Wine 1.4 under Ubuntu 12.04 x64)
393         if ((cpuSupport & CPU_TYPE_X64_SSX) && MUtils::OS::running_on_wine())
394         {
395                 qWarning("Running under Wine on a 64-Bit system. Going to disable all x64 support!\n");
396                 cpuSupport = CPU_TYPE_X86_SSE;
397         }
398
399         //Print the selected CPU type
400         qDebug("Selected CPU type is: %s", cpuTypeFriendlyName(cpuSupport));
401
402         //Allocate queues
403         QQueue<QString> queueToolName;
404         QQueue<QString> queueChecksum;
405         QQueue<QString> queueVersInfo;
406         QQueue<unsigned int> queueVersions;
407         QQueue<unsigned int> queueCpuTypes;
408
409         //Init properties
410         for(int i = 0; true; i++)
411         {
412                 if(!(g_lamexp_tools[i].pcName || g_lamexp_tools[i].pcHash  || g_lamexp_tools[i].uiVersion))
413                 {
414                         break;
415                 }
416                 else if(g_lamexp_tools[i].pcName && g_lamexp_tools[i].pcHash && g_lamexp_tools[i].uiVersion)
417                 {
418                         queueToolName.enqueue(QString::fromLatin1(g_lamexp_tools[i].pcName));
419                         queueChecksum.enqueue(QString::fromLatin1(g_lamexp_tools[i].pcHash));
420                         queueVersInfo.enqueue(QString::fromLatin1(g_lamexp_tools[i].pcVersTag));
421                         queueCpuTypes.enqueue(g_lamexp_tools[i].uiCpuType);
422                         queueVersions.enqueue(g_lamexp_tools[i].uiVersion);
423                 }
424                 else
425                 {
426                         qFatal("Inconsistent checksum data detected. Take care!");
427                 }
428         }
429
430         QDir appDir = QDir(QCoreApplication::applicationDirPath()).canonicalPath();
431
432         QScopedPointer<QThreadPool> pool(new QThreadPool());
433         pool->setMaxThreadCount((threadCount > 0) ? threadCount : qBound(2U, cores2threads(m_cpuFeatures.count), 16U));
434         ExtractorTask::clearFlags();
435
436         //Start the timer
437         QElapsedTimer timeExtractStart;
438         timeExtractStart.start();
439         
440         //Extract all files
441         while(!(queueToolName.isEmpty() || queueChecksum.isEmpty() || queueVersInfo.isEmpty() || queueCpuTypes.isEmpty() || queueVersions.isEmpty()))
442         {
443                 const QString toolName = queueToolName.dequeue();
444                 const QString checksum = queueChecksum.dequeue();
445                 const QString versInfo = queueVersInfo.dequeue();
446                 const unsigned int cpuType = queueCpuTypes.dequeue();
447                 const unsigned int version = queueVersions.dequeue();
448                         
449                 const QByteArray toolHash(checksum.toLatin1());
450                 if(toolHash.size() != 96)
451                 {
452                         qFatal("The checksum for \"%s\" has an invalid size!", MUTILS_UTF8(toolName));
453                         return -1.0;
454                 }
455                         
456                 QScopedPointer<QResource> resource(new QResource(QString(":/tools/%1").arg(toolName)));
457                 if(!(resource->isValid() && resource->data()))
458                 {
459                         qFatal("The resource for \"%s\" could not be found!", MUTILS_UTF8(toolName));
460                         return -1.0;
461                 }
462                         
463                 if(cpuType & cpuSupport)
464                 {
465                         pool->start(new ExtractorTask(resource.take(), appDir, toolName, toolHash, version, versInfo));
466                         continue;
467                 }
468         }
469
470         //Sanity Check
471         if(!(queueToolName.isEmpty() && queueChecksum.isEmpty() && queueVersInfo.isEmpty() && queueCpuTypes.isEmpty() && queueVersions.isEmpty()))
472         {
473                 qFatal("Checksum queues *not* empty fater verification completed. Take care!");
474         }
475
476         //Wait for extrator threads to finish
477         pool->waitForDone();
478
479         //Performance measure
480         const double delayExtract = double(timeExtractStart.elapsed()) / 1000.0;
481         timeExtractStart.invalidate();
482
483         //Make sure all files were extracted correctly
484         if(ExtractorTask::getExcept())
485         {
486                 char errorMsg[BUFF_SIZE];
487                 if(ExtractorTask::getErrMsg(errorMsg, BUFF_SIZE))
488                 {
489                         qFatal("At least one of the required tools could not be initialized:\n%s", errorMsg);
490                         return -1.0;
491                 }
492                 qFatal("At least one of the required tools could not be initialized!");
493                 return -1.0;
494         }
495
496         qDebug("All extracted.\n");
497
498         //Using any custom tools?
499         if(ExtractorTask::getCustom())
500         {
501                 qWarning("Warning: Using custom tools, you might encounter unexpected problems!\n");
502         }
503
504         //Check delay
505         if(delayExtract > g_allowedExtractDelay)
506         {
507                 m_slowIndicator = true;
508                 qWarning("Extracting tools took %.3f seconds -> probably slow realtime virus scanner.", delayExtract);
509                 qWarning("Please report performance problems to your anti-virus developer !!!\n");
510         }
511         else
512         {
513                 qDebug("Extracting the tools took %.3f seconds (OK).\n", delayExtract);
514         }
515
516         //Register all translations
517         initTranslations();
518
519         //Look for AAC encoders
520         InitAacEncTask::clearFlags();
521         for(size_t i = 0; g_lamexp_aacenc[i].toolName; i++)
522         {
523                 pool->start(new InitAacEncTask(&(g_lamexp_aacenc[i])));
524         }
525         pool->waitForDone();
526
527         //Make sure initialization finished correctly
528         if(InitAacEncTask::getExcept())
529         {
530                 char errorMsg[BUFF_SIZE];
531                 if(InitAacEncTask::getErrMsg(errorMsg, BUFF_SIZE))
532                 {
533                         qFatal("At least one optional component failed to initialize:\n%s", errorMsg);
534                         return -1.0;
535                 }
536                 qFatal("At least one optional component failed to initialize!");
537                 return -1.0;
538         }
539
540         m_bSuccess = true;
541         delay();
542
543         return delayExtract;
544 }
545
546 ////////////////////////////////////////////////////////////
547 // INTERNAL FUNCTIONS
548 ////////////////////////////////////////////////////////////
549
550 void InitializationThread::delay(void)
551 {
552         MUtils::OS::sleep_ms(333);
553 }
554
555 ////////////////////////////////////////////////////////////
556 // Translation Support
557 ////////////////////////////////////////////////////////////
558
559 void InitializationThread::initTranslations(void)
560 {
561         //Search for language files
562         const QDir qmDirectory(":/localization");
563         const QStringList qmFiles = qmDirectory.entryList(QStringList() << "LameXP_??.qm", QDir::Files, QDir::Name);
564
565         //Make sure we found at least one translation
566         if(qmFiles.count() < 1)
567         {
568                 qFatal("Could not find any translation files!");
569                 return;
570         }
571
572         //Initialize variables
573         const QString langResTemplate(":/localization/%1.txt");
574         QRegExp langIdExp("^LameXP_(\\w\\w)\\.qm$", Qt::CaseInsensitive);
575
576         //Add all available translations
577         for(QStringList::ConstIterator iter = qmFiles.constBegin(); iter != qmFiles.constEnd(); iter++)
578         {
579                 const QString langFile = qmDirectory.absoluteFilePath(*iter);
580                 QString langId, langName;
581                 unsigned int systemId = 0, country = 0;
582                 
583                 if(QFileInfo(langFile).isFile() && (langIdExp.indexIn(*iter) >= 0))
584                 {
585                         langId = langIdExp.cap(1).toLower();
586                         QScopedPointer<QResource> langRes(new QResource(langResTemplate.arg(*iter)));
587                         if(langRes->isValid() && langRes->size() > 0)
588                         {
589                                 QByteArray data = QByteArray::fromRawData(reinterpret_cast<const char*>(langRes->data()), langRes->size());
590                                 QTextStream stream(&data, QIODevice::ReadOnly);
591                                 stream.setAutoDetectUnicode(false); stream.setCodec("UTF-8");
592
593                                 while(!(stream.atEnd() || (stream.status() != QTextStream::Ok)))
594                                 {
595                                         QStringList langInfo = stream.readLine().simplified().split(",", QString::SkipEmptyParts);
596                                         if(langInfo.count() >= 3)
597                                         {
598                                                 systemId = langInfo.at(0).trimmed().toUInt();
599                                                 country  = langInfo.at(1).trimmed().toUInt();
600                                                 langName = langInfo.at(2).trimmed();
601                                                 break;
602                                         }
603                                 }
604                         }
605                 }
606
607                 if(!(langId.isEmpty() || langName.isEmpty() || (systemId == 0)))
608                 {
609                         if(MUtils::Translation::insert(langId, langFile, langName, systemId, country))
610                         {
611                                 qDebug("Registering translation: %s = %s (%u) [%u]", MUTILS_UTF8(*iter), MUTILS_UTF8(langName), systemId, country);
612                         }
613                         else
614                         {
615                                 qWarning("Failed to register: %s", langFile.toLatin1().constData());
616                         }
617                 }
618         }
619
620         qDebug("All registered.\n");
621 }
622
623 ////////////////////////////////////////////////////////////
624 // AAC Encoder Detection
625 ////////////////////////////////////////////////////////////
626
627 void InitAacEncTask::initAacEncImpl(const char *const toolName, const char *const fileNames[], const QStringList &checkArgs, const quint32 &toolMinVersion, const quint32 &verDigits, const quint32 &verShift, const char *const verStr, QRegExp &regExpVer, QRegExp &regExpSig, const QList<QRegExp> &regExpLib)
628 {
629         static const size_t MAX_FILES = 8;
630         const QString appPath = QDir(QCoreApplication::applicationDirPath()).canonicalPath();
631         
632         QFileInfoList fileInfo;
633         for(size_t i = 0; fileNames[i] && (fileInfo.count() < MAX_FILES); i++)
634         {
635                 fileInfo.append(QFileInfo(QString("%1/%2").arg(appPath, QString::fromLatin1(fileNames[i]))));
636         }
637         
638         for(QFileInfoList::ConstIterator iter = fileInfo.constBegin(); iter != fileInfo.constEnd(); iter++)
639         {
640                 if(!(iter->exists() && iter->isFile()))
641                 {
642                         qDebug("%s encoder binaries not found -> Encoding support will be disabled!\n", toolName);
643                         return;
644                 }
645                 if((iter->suffix().compare("exe", Qt::CaseInsensitive) == 0) && (!MUtils::OS::is_executable_file(iter->canonicalFilePath())))
646                 {
647                         qDebug("%s executable is invalid -> %s support will be disabled!\n", MUTILS_UTF8(iter->fileName()), toolName);
648                         return;
649                 }
650         }
651
652         qDebug("Found %s encoder binary:\n%s\n", toolName, MUTILS_UTF8(fileInfo.first().canonicalFilePath()));
653
654         //Lock the encoder binaries
655         QScopedPointer<LockedFile> binaries[MAX_FILES];
656         try
657         {
658                 size_t index = 0;
659                 for(QFileInfoList::ConstIterator iter = fileInfo.constBegin(); iter != fileInfo.constEnd(); iter++)
660                 {
661                         binaries[index++].reset(new LockedFile(iter->canonicalFilePath()));
662                 }
663         }
664         catch(...)
665         {
666                 qWarning("Failed to get excluive lock to encoder binary -> %s support will be disabled!", toolName);
667                 return;
668         }
669
670         QProcess process;
671         MUtils::init_process(process, fileInfo.first().absolutePath());
672         process.start(fileInfo.first().canonicalFilePath(), checkArgs);
673
674         if(!process.waitForStarted())
675         {
676                 qWarning("%s process failed to create!", toolName);
677                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
678                 process.kill();
679                 process.waitForFinished(-1);
680                 return;
681         }
682
683         quint32 toolVersion = quint32(-1);
684         bool sigFound = regExpSig.isEmpty() ? true : false;
685         QVector<bool> extraLib(regExpLib.count(), false);
686
687         while(process.state() != QProcess::NotRunning)
688         {
689                 if(!process.waitForReadyRead())
690                 {
691                         if(process.state() == QProcess::Running)
692                         {
693                                 qWarning("%s process time out -> killing!", toolName);
694                                 process.kill();
695                                 process.waitForFinished(-1);
696                                 return;
697                         }
698                 }
699                 while(process.canReadLine())
700                 {
701                         const QString line = QString::fromUtf8(process.readLine().constData()).simplified();
702                         if (!sigFound)
703                         {
704                                 sigFound = (regExpSig.lastIndexIn(line) >= 0);
705                         }
706                         if (sigFound)
707                         {
708                                 if (regExpVer.lastIndexIn(line) >= 0)
709                                 {
710                                         quint32 tmp[8];
711                                         if (MUtils::regexp_parse_uint32(regExpVer, tmp, qMin(verDigits, 8U)))
712                                         {
713                                                 toolVersion = 0;
714                                                 for (quint32 i = 0; i < verDigits; i++)
715                                                 {
716                                                         toolVersion = (verShift > 0) ? ((toolVersion * verShift) + qBound(0U, tmp[i], (verShift - 1))) : tmp[i];
717                                                 }
718                                         }
719                                 }
720                                 for (int i = 0; i < regExpLib.count(); ++i)
721                                 {
722                                         extraLib[i] = extraLib[i] || (regExpLib[i].lastIndexIn(line) >= 0);
723                                 }
724                         }
725                 }
726         }
727
728         if((toolVersion == 0) || (toolVersion == quint32(-1)))
729         {
730                 qWarning("%s version could not be determined -> Encoding support will be disabled!", toolName);
731                 return;
732         }
733
734         if(toolVersion < toolMinVersion)
735         {
736                 qWarning("%s version is too much outdated (%s) -> Encoding support will be disabled!", toolName, MUTILS_UTF8(lamexp_version2string(verStr, toolVersion,    "N/A")));
737                 qWarning("Minimum required %s version currently is: %s\n",                             toolName, MUTILS_UTF8(lamexp_version2string(verStr, toolMinVersion, "N/A")));
738                 return;
739         }
740
741         for (int i = 0; i < extraLib.count(); ++i)
742         {
743                 if (!extraLib[i])
744                 {
745                         qWarning("%s lacks companion library (%s) -> Encoding support will be disabled!\n", toolName, MUTILS_UTF8(regExpLib[i].pattern()));
746                         return;
747                 }
748         }
749         
750         qDebug("Enabled %s encoder %s.\n", toolName, MUTILS_UTF8(lamexp_version2string(verStr, toolVersion, "N/A")));
751
752         size_t index = 0;
753         for(QFileInfoList::ConstIterator iter = fileInfo.constBegin(); iter != fileInfo.constEnd(); iter++)
754         {
755                 lamexp_tools_register(iter->fileName(), binaries[index++].take(), toolVersion);
756         }
757 }
758
759 ////////////////////////////////////////////////////////////
760 // Self-Test Function
761 ////////////////////////////////////////////////////////////
762
763 void InitializationThread::selfTest(void)
764 {
765         static const unsigned int CPU[7] = { CPU_TYPE_X86_GEN, CPU_TYPE_X86_SSE, CPU_TYPE_X86_AVX, CPU_TYPE_X64_SSE, CPU_TYPE_X64_AVX, 0U };
766
767         unsigned int count = 0U, expectedCount = UINT_MAX;
768         for(size_t k = 0U; CPU[k]; count = 0U, ++k)
769         {
770                 const unsigned int cpuSupport = CPU[k];
771                 qDebug("[SELF-TEST]");
772                 qDebug("Testing CPU type: %s", cpuTypeFriendlyName(cpuSupport));
773                 for (int i = 0; g_lamexp_tools[i].pcName || g_lamexp_tools[i].pcHash || g_lamexp_tools[i].uiVersion; ++i)
774                 {
775                         if (g_lamexp_tools[i].pcName && g_lamexp_tools[i].pcHash && g_lamexp_tools[i].uiVersion)
776                         {
777                                 const QString toolName = QString::fromLatin1(g_lamexp_tools[i].pcName);
778                                 const QByteArray expectedHash = QByteArray(g_lamexp_tools[i].pcHash);
779                                 if(g_lamexp_tools[i].uiCpuType & cpuSupport)
780                                 {
781                                         qDebug("%2u -> %s", ++count, MUTILS_UTF8(toolName));
782                                         QFile resource(QString(":/tools/%1").arg(toolName));
783                                         if(!resource.open(QIODevice::ReadOnly))
784                                         {
785                                                 qFatal("The resource for \"%s\" could not be opened!", MUTILS_UTF8(toolName));
786                                                 break;
787                                         }
788                                         QByteArray hash = FileHash::computeHash(resource);
789                                         if(hash.isNull() || _stricmp(hash.constData(), expectedHash.constData()))
790                                         {
791                                                 qFatal("Hash check for tool \"%s\" has failed!", MUTILS_UTF8(toolName));
792                                                 break;
793                                         }
794                                         resource.close();
795                                 }
796                         }
797                         else
798                         {
799                                 qFatal("Inconsistent checksum data detected. Take care!");
800                         }
801                 }
802                 if (k != 0U)
803                 {
804                         if (count != expectedCount)
805                         {
806                                 qFatal("Tool count mismatch for CPU type %u. Should be %u, but got %u !!!", cpuSupport, expectedCount, count);
807                         }
808                 }
809                 else
810                 {
811                         expectedCount = count; /*remember count*/
812                 }
813                 qDebug("Done.\n");
814         }
815 }
816
817 void InitializationThread::runSyncronized(void)
818 {
819         run();
820 }
821
822 ////////////////////////////////////////////////////////////
823 // EVENTS
824 ////////////////////////////////////////////////////////////
825
826 /*NONE*/