OSDN Git Service

Small improvement to init_process() function.
[mutilities/MUtilities.git] / src / Global.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2018 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library 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 GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 //
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
21
22 #if _MSC_VER
23 #define _CRT_RAND_S 1
24 #endif
25
26 //MUtils
27 #include <MUtils/Global.h>
28 #include <MUtils/OSSupport.h>
29 #include <MUtils/Version.h>
30
31 //Internal
32 #include "DirLocker.h"
33 #include "3rd_party/strnatcmp/include/strnatcmp.h"
34
35 //Qt
36 #include <QDir>
37 #include <QReadWriteLock>
38 #include <QProcess>
39 #include <QTextCodec>
40 #include <QPair>
41 #include <QHash>
42 #include <QListIterator>
43 #include <QMutex>
44 #include <QThreadStorage>
45
46 //CRT
47 #include <cstdlib>
48 #include <ctime>
49 #include <process.h>
50
51 //VLD
52 #ifdef _MSC_VER
53 #include <vld.h>
54 #endif
55
56 ///////////////////////////////////////////////////////////////////////////////
57 // Random Support
58 ///////////////////////////////////////////////////////////////////////////////
59
60 #ifndef _CRT_RAND_S
61 #define rand_s(X) (true)
62 #endif
63
64 //Per-thread init flag
65 static QThreadStorage<bool> g_srand_flag;
66
67 //32-Bit wrapper for qrand()
68 #define QRAND() ((static_cast<quint32>(qrand()) & 0xFFFF) | (static_cast<quint32>(qrand()) << 16U))
69
70 //Robert Jenkins' 96 bit Mix Function
71 static quint32 mix_function(quint32 a, quint32 b, quint32 c)
72 {
73         a=a-b;  a=a-c;  a=a^(c >> 13);
74         b=b-c;  b=b-a;  b=b^(a <<  8); 
75         c=c-a;  c=c-b;  c=c^(b >> 13);
76         a=a-b;  a=a-c;  a=a^(c >> 12);
77         b=b-c;  b=b-a;  b=b^(a << 16);
78         c=c-a;  c=c-b;  c=c^(b >>  5);
79         a=a-b;  a=a-c;  a=a^(c >>  3);
80         b=b-c;  b=b-a;  b=b^(a << 10);
81         c=c-a;  c=c-b;  c=c^(b >> 15);
82
83         return a ^ b ^ c;
84 }
85
86 static void seed_rand(void)
87 {
88         QDateTime build(MUtils::Version::lib_build_date(), MUtils::Version::lib_build_time());
89         const quint32 seed_0 = mix_function(MUtils::OS::process_id(), MUtils::OS::thread_id(), build.toMSecsSinceEpoch());
90         qsrand(mix_function(clock(), time(NULL), seed_0));
91 }
92
93 static quint32 rand_fallback(void)
94 {
95         Q_ASSERT(RAND_MAX >= 0x7FFF);
96
97         if (!(g_srand_flag.hasLocalData() && g_srand_flag.localData()))
98         {
99                 seed_rand();
100                 g_srand_flag.setLocalData(true);
101         }
102
103         quint32 rnd_val = mix_function(0x32288EA3, clock(), time(NULL));
104
105         for (size_t i = 0; i < 42; i++)
106         {
107                 rnd_val = mix_function(rnd_val, QRAND(), QRAND());
108                 rnd_val = mix_function(QRAND(), rnd_val, QRAND());
109                 rnd_val = mix_function(QRAND(), QRAND(), rnd_val);
110         }
111
112         return rnd_val;
113 }
114
115 quint32 MUtils::next_rand_u32(void)
116 {
117         quint32 rnd;
118         if (rand_s(&rnd))
119         {
120                 return rand_fallback();
121         }
122         return rnd;
123 }
124
125 quint32 MUtils::next_rand_u32(const quint32 max)
126 {
127         static const uint32_t DIV_LUT[64] =
128         {
129                 0xFFFFFFFF, 0xFFFFFFFF, 0x80000000, 0x55555556, 0x40000000, 0x33333334, 0x2AAAAAAB, 0x24924925,
130                 0x20000000, 0x1C71C71D, 0x1999999A, 0x1745D175, 0x15555556, 0x13B13B14, 0x12492493, 0x11111112,
131                 0x10000000, 0x0F0F0F10, 0x0E38E38F, 0x0D79435F, 0x0CCCCCCD, 0x0C30C30D, 0x0BA2E8BB, 0x0B21642D,
132                 0x0AAAAAAB, 0x0A3D70A4, 0x09D89D8A, 0x097B425F, 0x0924924A, 0x08D3DCB1, 0x08888889, 0x08421085,
133                 0x08000000, 0x07C1F07D, 0x07878788, 0x07507508, 0x071C71C8, 0x06EB3E46, 0x06BCA1B0, 0x06906907,
134                 0x06666667, 0x063E7064, 0x06186187, 0x05F417D1, 0x05D1745E, 0x05B05B06, 0x0590B217, 0x0572620B,
135                 0x05555556, 0x0539782A, 0x051EB852, 0x05050506, 0x04EC4EC5, 0x04D4873F, 0x04BDA130, 0x04A7904B,
136                 0x04924925, 0x047DC120, 0x0469EE59, 0x0456C798, 0x04444445, 0x04325C54, 0x04210843, 0x04104105
137         };
138         return (max < 64) ? (next_rand_u32() / DIV_LUT[max]) : (next_rand_u32() / (UINT32_MAX / max + 1U));
139 }
140
141
142 quint64 MUtils::next_rand_u64(void)
143 {
144         return (quint64(next_rand_u32()) << 32) | quint64(next_rand_u32());
145 }
146
147 QString MUtils::next_rand_str(const bool &bLong)
148 {
149         if (bLong)
150         {
151                 return next_rand_str(false) + next_rand_str(false);
152         }
153         else
154         {
155                 return QString::number(next_rand_u64(), 16).rightJustified(16, QLatin1Char('0'));
156         }
157 }
158
159 ///////////////////////////////////////////////////////////////////////////////
160 // STRING UTILITY FUNCTIONS
161 ///////////////////////////////////////////////////////////////////////////////
162
163 static QScopedPointer<QRegExp> g_str_trim_rx_r;
164 static QScopedPointer<QRegExp> g_str_trim_rx_l;
165 static QMutex                  g_str_trim_lock;
166
167 static QString& trim_helper(QString &str, QScopedPointer<QRegExp> &regex, const char *const pattern)
168 {
169         QMutexLocker lock(&g_str_trim_lock);
170         if (regex.isNull())
171         {
172                 regex.reset(new QRegExp(QLatin1String(pattern)));
173         }
174         str.remove(*regex.data());
175         return str;
176 }
177
178 QString& MUtils::trim_right(QString &str)
179 {
180         static const char *const TRIM_RIGHT = "\\s+$";
181         return trim_helper(str, g_str_trim_rx_r, TRIM_RIGHT);
182 }
183
184 QString& MUtils::trim_left(QString &str)
185 {
186         static const char *const TRIM_LEFT = "^\\s+";
187         return trim_helper(str, g_str_trim_rx_l, TRIM_LEFT);
188 }
189
190 QString MUtils::trim_right(const QString &str)
191 {
192         QString temp(str);
193         return trim_right(temp);
194 }
195
196 QString MUtils::trim_left(const QString &str)
197 {
198         QString temp(str);
199         return trim_left(temp);
200 }
201
202 ///////////////////////////////////////////////////////////////////////////////
203 // GENERATE FILE NAME
204 ///////////////////////////////////////////////////////////////////////////////
205
206 QString MUtils::make_temp_file(const QString &basePath, const QString &extension, const bool placeholder)
207 {
208         return make_temp_file(QDir(basePath), extension, placeholder);
209 }
210
211 QString MUtils::make_temp_file(const QDir &basePath, const QString &extension, const bool placeholder)
212 {
213         if (extension.isEmpty())
214         {
215                 qWarning("Cannot generate temp file name with invalid parameters!");
216                 return QString();
217         }
218
219         for(int i = 0; i < 4096; i++)
220         {
221                 const QString tempFileName = basePath.absoluteFilePath(QString("%1.%2").arg(next_rand_str(), extension));
222                 if(!QFileInfo(tempFileName).exists())
223                 {
224                         if(placeholder)
225                         {
226                                 QFile file(tempFileName);
227                                 if(file.open(QFile::ReadWrite))
228                                 {
229                                         file.close();
230                                         return tempFileName;
231                                 }
232                         }
233                         else
234                         {
235                                 return tempFileName;
236                         }
237                 }
238         }
239
240         qWarning("Failed to generate temp file name!");
241         return QString();
242 }
243
244 QString MUtils::make_unique_file(const QString &basePath, const QString &baseName, const QString &extension, const bool fancy, const bool placeholder)
245 {
246         return make_unique_file(QDir(basePath), baseName, extension, fancy);
247 }
248
249 QString MUtils::make_unique_file(const QDir &basePath, const QString &baseName, const QString &extension, const bool fancy, const bool placeholder)
250 {
251         if (baseName.isEmpty() || extension.isEmpty())
252         {
253                 qWarning("Cannot generate unique file name with invalid parameters!");
254                 return QString();
255         }
256
257         quint32 n = fancy ? 2 : 0;
258         QString fileName = fancy ? basePath.absoluteFilePath(QString("%1.%2").arg(baseName, extension)) : QString();
259         while (fileName.isEmpty() || QFileInfo(fileName).exists())
260         {
261                 if (n <= quint32(USHRT_MAX))
262                 {
263                         if (fancy)
264                         {
265                                 fileName = basePath.absoluteFilePath(QString("%1 (%2).%3").arg(baseName, QString::number(n++), extension));
266                         }
267                         else
268                         {
269                                 fileName = basePath.absoluteFilePath(QString("%1.%2.%3").arg(baseName, QString::number(n++, 16).rightJustified(4, QLatin1Char('0')), extension));
270                         }
271                 }
272                 else
273                 {
274                         qWarning("Failed to generate unique file name!");
275                         return QString();
276                 }
277         }
278
279         if (placeholder && (!fileName.isEmpty()))
280         {
281                 QFile placeholder(fileName);
282                 if (placeholder.open(QIODevice::WriteOnly))
283                 {
284                         placeholder.close();
285                 }
286         }
287
288         return fileName;
289 }
290
291 ///////////////////////////////////////////////////////////////////////////////
292 // COMPUTE PARITY
293 ///////////////////////////////////////////////////////////////////////////////
294
295 /*
296  * Compute parity in parallel
297  * http://www.graphics.stanford.edu/~seander/bithacks.html#ParityParallel
298  */
299 bool MUtils::parity(quint32 value)
300 {
301         value ^= value >> 16;
302         value ^= value >> 8;
303         value ^= value >> 4;
304         value &= 0xf;
305         return ((0x6996 >> value) & 1) != 0;
306 }
307
308 ///////////////////////////////////////////////////////////////////////////////
309 // TEMP FOLDER
310 ///////////////////////////////////////////////////////////////////////////////
311
312 static QScopedPointer<MUtils::Internal::DirLock> g_temp_folder_file;
313 static QReadWriteLock                            g_temp_folder_lock;
314
315 static QString try_create_subfolder(const QString &baseDir, const QString &postfix)
316 {
317         const QString baseDirPath = QDir(baseDir).absolutePath();
318         for(int i = 0; i < 32; i++)
319         {
320                 QDir directory(baseDirPath);
321                 if(directory.mkpath(postfix) && directory.cd(postfix))
322                 {
323                         return directory.canonicalPath();
324                 }
325         }
326         return QString();
327 }
328
329 static MUtils::Internal::DirLock *try_init_temp_folder(const QString &baseDir)
330 {
331         const QString tempPath = try_create_subfolder(baseDir, MUtils::next_rand_str());
332         if(!tempPath.isEmpty())
333         {
334                 for(int i = 0; i < 32; i++)
335                 {
336                         MUtils::Internal::DirLock *lockFile = NULL;
337                         try
338                         {
339                                 lockFile = new MUtils::Internal::DirLock(tempPath);
340                                 return lockFile;
341                         }
342                         catch(MUtils::Internal::DirLockException&)
343                         {
344                                 /*ignore error and try again*/
345                         }
346                 }
347         }
348         return NULL;
349 }
350
351 static bool temp_folder_cleanup_helper(const QString &tempPath)
352 {
353         size_t delay = 1;
354         static const size_t MAX_DELAY = 8192;
355         forever
356         {
357                 QDir::setCurrent(QDir::rootPath());
358                 if(MUtils::remove_directory(tempPath, true))
359                 {
360                         return true;
361                 }
362                 else
363                 {
364                         if(delay > MAX_DELAY)
365                         {
366                                 return false;
367                         }
368                         MUtils::OS::sleep_ms(delay);
369                         delay *= 2;
370                 }
371         }
372 }
373
374 static void temp_folder_cleaup(void)
375 {
376         QWriteLocker writeLock(&g_temp_folder_lock);
377
378         //Clean the directory
379         while(!g_temp_folder_file.isNull())
380         {
381                 const QString tempPath = g_temp_folder_file->getPath();
382                 g_temp_folder_file.reset(NULL);
383                 if(!temp_folder_cleanup_helper(tempPath))
384                 {
385                         MUtils::OS::system_message_wrn(L"Temp Cleaner", L"Warning: Not all temporary files could be removed!");
386                 }
387         }
388 }
389
390 const QString &MUtils::temp_folder(void)
391 {
392         QReadLocker readLock(&g_temp_folder_lock);
393
394         //Already initialized?
395         if(!g_temp_folder_file.isNull())
396         {
397                 return g_temp_folder_file->getPath();
398         }
399
400         //Obtain the write lock to initilaize
401         readLock.unlock();
402         QWriteLocker writeLock(&g_temp_folder_lock);
403         
404         //Still uninitilaized?
405         if(!g_temp_folder_file.isNull())
406         {
407                 return g_temp_folder_file->getPath();
408         }
409
410         //Try the %TMP% or %TEMP% directory first
411         if(MUtils::Internal::DirLock *lockFile = try_init_temp_folder(QDir::tempPath()))
412         {
413                 g_temp_folder_file.reset(lockFile);
414                 atexit(temp_folder_cleaup);
415                 return lockFile->getPath();
416         }
417
418         qWarning("%%TEMP%% directory not found -> trying fallback mode now!");
419         static const OS::known_folder_t FOLDER_ID[2] = { OS::FOLDER_LOCALAPPDATA, OS::FOLDER_SYSTROOT_DIR };
420         for(size_t id = 0; id < 2; id++)
421         {
422                 const QString &knownFolder = OS::known_folder(FOLDER_ID[id]);
423                 if(!knownFolder.isEmpty())
424                 {
425                         const QString tempRoot = try_create_subfolder(knownFolder, QLatin1String("TEMP"));
426                         if(!tempRoot.isEmpty())
427                         {
428                                 if(MUtils::Internal::DirLock *lockFile = try_init_temp_folder(tempRoot))
429                                 {
430                                         g_temp_folder_file.reset(lockFile);
431                                         atexit(temp_folder_cleaup);
432                                         return lockFile->getPath();
433                                 }
434                         }
435                 }
436         }
437
438         qFatal("Temporary directory could not be initialized !!!");
439         return (*((QString*)NULL));
440 }
441
442 ///////////////////////////////////////////////////////////////////////////////
443 // REMOVE DIRECTORY / FILE
444 ///////////////////////////////////////////////////////////////////////////////
445
446 static const QFile::Permissions FILE_PERMISSIONS_NONE = QFile::ReadOther | QFile::WriteOther;
447
448 bool MUtils::remove_file(const QString &fileName)
449 {
450         QFileInfo fileInfo(fileName);
451
452         for(size_t round = 0; round < 13; ++round)
453         {
454                 if (round > 0)
455                 {
456                         MUtils::OS::sleep_ms(round);
457                         fileInfo.refresh();
458                 }
459                 if (fileInfo.exists())
460                 {
461                         QFile file(fileName);
462                         if (round > 0)
463                         {
464                                 file.setPermissions(FILE_PERMISSIONS_NONE);
465                         }
466                         file.remove();
467                         fileInfo.refresh();
468                 }
469                 if (!fileInfo.exists())
470                 {
471                         return true; /*success*/
472                 }
473         }
474
475         qWarning("Could not delete \"%s\"", MUTILS_UTF8(fileName));
476         return false;
477 }
478
479 bool MUtils::remove_directory(const QString &folderPath, const bool &recursive)
480 {
481         const QDir folder(folderPath);
482
483         if(recursive && folder.exists())
484         {
485                 const QFileInfoList entryList = folder.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
486                 for(QFileInfoList::ConstIterator iter = entryList.constBegin(); iter != entryList.constEnd(); iter++)
487                 {
488                         if(iter->isDir())
489                         {
490                                 remove_directory(iter->canonicalFilePath(), true);
491                         }
492                         else
493                         {
494                                 remove_file(iter->canonicalFilePath());
495                         }
496                 }
497         }
498
499         for(size_t round = 0; round < 13; ++round)
500         {
501                 if(round > 0)
502                 {
503                         MUtils::OS::sleep_ms(round);
504                         folder.refresh();
505                 }
506                 if (folder.exists())
507                 {
508                         QDir parent = folder;
509                         if (parent.cdUp())
510                         {
511                                 if (round > 0)
512                                 {
513                                         QFile::setPermissions(folder.absolutePath(), FILE_PERMISSIONS_NONE);
514                                 }
515                                 parent.rmdir(folder.dirName());
516                                 folder.refresh();
517                         }
518                 }
519                 if (!folder.exists())
520                 {
521                         return true; /*success*/
522                 }
523         }
524         
525         qWarning("Could not rmdir \"%s\"", MUTILS_UTF8(folderPath));
526         return false;
527 }
528
529 ///////////////////////////////////////////////////////////////////////////////
530 // PROCESS UTILS
531 ///////////////////////////////////////////////////////////////////////////////
532
533 static void prependToPath(QProcessEnvironment &env, const QString &value)
534 {
535         static const QLatin1String PATH("PATH");
536         const QString path = env.value(PATH, QString()).trimmed();
537         env.insert(PATH, path.isEmpty() ? value : QString("%1;%2").arg(value, path));
538 }
539
540 void MUtils::init_process(QProcess &process, const QString &wokringDir, const bool bReplaceTempDir, const QStringList *const extraPaths, const QHash<QString, QString> *const extraEnv)
541 {
542         //Environment variable names
543         static const char *const ENVVAR_NAMES_TMP[] =
544         {
545                 "TEMP", "TMP", "TMPDIR", "HOME", "USERPROFILE", "HOMEPATH", NULL
546         };
547         static const char *const ENVVAR_NAMES_SYS[] =
548         {
549                 "WINDIR", "SYSTEMROOT", NULL
550         };
551         static const char *const ENVVAR_NAMES_DEL[] =
552         {
553                 "HTTP_PROXY", "FTP_PROXY", "NO_PROXY", "HOME", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
554                 "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LANG", NULL
555         };
556
557         //Initialize environment
558         QProcessEnvironment env = process.processEnvironment();
559         if (env.isEmpty())
560         {
561                 env = QProcessEnvironment::systemEnvironment();
562         }
563
564         //Clean enviroment variables that might affect our tools
565         for(const char *const *ptr = ENVVAR_NAMES_DEL; *ptr; ++ptr)
566         {
567                 env.remove(QString::fromLatin1(*ptr));
568         }
569
570         //Set up system root directory
571         const QString sysRoot = QDir::toNativeSeparators(OS::known_folder(OS::FOLDER_SYSTROOT_DIR));
572         if (!sysRoot.isEmpty())
573         {
574                 for (const char *const *ptr = ENVVAR_NAMES_SYS; *ptr; ++ptr)
575                 {
576                         env.insert(QString::fromLatin1(*ptr), sysRoot);
577                 }
578         }
579
580         //Replace TEMP directory in environment
581         const QString tempDir = QDir::toNativeSeparators(temp_folder());
582         if(bReplaceTempDir)
583         {
584                 for (const char *const *ptr = ENVVAR_NAMES_TMP; *ptr; ++ptr)
585                 {
586                         env.insert(QString::fromLatin1(*ptr), tempDir);
587                 }
588         }
589
590         //Setup PATH variable
591         prependToPath(env, tempDir);
592         if (extraPaths && (!extraPaths->isEmpty()))
593         {
594                 QListIterator<QString> iter(*extraPaths);
595                 iter.toBack();
596                 while (iter.hasPrevious())
597                 {
598                         prependToPath(env, QDir::toNativeSeparators(iter.previous()));
599                 }
600         }
601         
602         //Setup environment
603         if (extraEnv && (!extraEnv->isEmpty()))
604         {
605                 for (QHash<QString, QString>::ConstIterator iter = extraEnv->constBegin(); iter != extraEnv->constEnd(); iter++)
606                 {
607                         env.insert(iter.key(), iter.value());
608                 }
609         }
610
611         //Setup QPorcess object
612         process.setWorkingDirectory(wokringDir);
613         process.setProcessChannelMode(QProcess::MergedChannels);
614         process.setReadChannel(QProcess::StandardOutput);
615         process.setProcessEnvironment(env);
616 }
617
618 ///////////////////////////////////////////////////////////////////////////////
619 // NATURAL ORDER STRING COMPARISON
620 ///////////////////////////////////////////////////////////////////////////////
621
622 static bool natural_string_sort_helper(const QString &str1, const QString &str2)
623 {
624         return (MUtils::Internal::NaturalSort::strnatcmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
625 }
626
627 static bool natural_string_sort_helper_fold_case(const QString &str1, const QString &str2)
628 {
629         return (MUtils::Internal::NaturalSort::strnatcasecmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
630 }
631
632 void MUtils::natural_string_sort(QStringList &list, const bool bIgnoreCase)
633 {
634         qSort(list.begin(), list.end(), bIgnoreCase ? natural_string_sort_helper_fold_case : natural_string_sort_helper);
635 }
636
637 ///////////////////////////////////////////////////////////////////////////////
638 // CLEAN FILE PATH
639 ///////////////////////////////////////////////////////////////////////////////
640
641 static QMutex                                              g_clean_file_name_mutex;
642 static QScopedPointer<const QList<QPair<QRegExp,QString>>> g_clean_file_name_regex;
643
644 static void clean_file_name_make_pretty(QString &str)
645 {
646         static const struct { const char *p; const char *r; } PATTERN[] =
647         {
648                 { "^\\s*\"([^\"]*)\"\\s*$",    "\\1"                         },  //Remove straight double quotes around the whole string
649                 { "\"([^\"]*)\"",              "\xE2\x80\x9C\\1\xE2\x80\x9D" },  //Replace remaining pairs of straight double quotes with opening/closing double quote
650                 { "^[\\\\/:]+([^\\\\/:]+.*)$", "\\1"                         },  //Remove leading slash, backslash and colon characters
651                 { "^(.*[^\\\\/:]+)[\\\\/:]+$", "\\1"                         },  //Remove trailing slash, backslash and colon characters
652                 { "(\\s*[\\\\/:]\\s*)+",       " - "                         },  //Replace any slash, backslash or colon character that appears in the middle
653                 { NULL, NULL }
654         };
655
656         QMutexLocker locker(&g_clean_file_name_mutex);
657
658         if (g_clean_file_name_regex.isNull())
659         {
660                 QScopedPointer<QList<QPair<QRegExp, QString>>> list(new QList<QPair<QRegExp, QString>>());
661                 for (size_t i = 0; PATTERN[i].p; ++i)
662                 {
663                         list->append(qMakePair(QRegExp(QString::fromUtf8(PATTERN[i].p), Qt::CaseInsensitive), PATTERN[i].r ? QString::fromUtf8(PATTERN[i].r) : QString()));
664                 }
665                 g_clean_file_name_regex.reset(list.take());
666         }
667
668         bool keepOnGoing = !str.isEmpty();
669         while(keepOnGoing)
670         {
671                 const QString prev = str;
672                 keepOnGoing = false;
673                 for (QList<QPair<QRegExp, QString>>::ConstIterator iter = g_clean_file_name_regex->constBegin(); iter != g_clean_file_name_regex->constEnd(); ++iter)
674                 {
675                         str.replace(iter->first, iter->second);
676                         if (str.compare(prev))
677                         {
678                                 str = str.simplified();
679                                 keepOnGoing = !str.isEmpty();
680                                 break;
681                         }
682                 }
683         }
684 }
685
686 QString MUtils::clean_file_name(const QString &name, const bool &pretty)
687 {
688         static const QLatin1Char REPLACEMENT_CHAR('_');
689         static const char FILENAME_ILLEGAL_CHARS[] = "<>:\"/\\|?*";
690         static const char *const FILENAME_RESERVED_NAMES[] =
691         {
692                 "CON", "PRN", "AUX", "NUL",
693                 "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
694                 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", NULL
695         };
696
697         QString result(name);
698         if (pretty)
699         {
700                 clean_file_name_make_pretty(result);
701         }
702
703         for(QString::Iterator iter = result.begin(); iter != result.end(); iter++)
704         {
705                 if (iter->category() == QChar::Other_Control)
706                 {
707                         *iter = REPLACEMENT_CHAR;
708                 }
709         }
710                 
711         for(size_t i = 0; FILENAME_ILLEGAL_CHARS[i]; i++)
712         {
713                 result.replace(QLatin1Char(FILENAME_ILLEGAL_CHARS[i]), REPLACEMENT_CHAR);
714         }
715         
716         trim_right(result);
717         while (result.endsWith(QLatin1Char('.')))
718         {
719                 result.chop(1);
720                 trim_right(result);
721         }
722
723         for (size_t i = 0; FILENAME_RESERVED_NAMES[i]; i++)
724         {
725                 const QString reserved = QString::fromLatin1(FILENAME_RESERVED_NAMES[i]);
726                 if ((!result.compare(reserved, Qt::CaseInsensitive)) || result.startsWith(reserved + QLatin1Char('.'), Qt::CaseInsensitive))
727                 {
728                         result.replace(0, reserved.length(), QString().leftJustified(reserved.length(), REPLACEMENT_CHAR));
729                 }
730         }
731
732         return result;
733 }
734
735 static QPair<QString,QString> clean_file_path_get_prefix(const QString path)
736 {
737         static const char *const PREFIXES[] =
738         {
739                 "//?/", "//", "/", NULL
740         };
741         const QString posixPath = QDir::fromNativeSeparators(path.trimmed());
742         for (int i = 0; PREFIXES[i]; i++)
743         {
744                 const QString prefix = QString::fromLatin1(PREFIXES[i]);
745                 if (posixPath.startsWith(prefix))
746                 {
747                         return qMakePair(prefix, posixPath.mid(prefix.length()));
748                 }
749         }
750         return qMakePair(QString(), posixPath);
751 }
752
753 QString MUtils::clean_file_path(const QString &path, const bool &pretty)
754 {
755         const QPair<QString, QString> prefix = clean_file_path_get_prefix(path);
756
757         QStringList parts = prefix.second.split(QLatin1Char('/'), QString::SkipEmptyParts);
758         for(int i = 0; i < parts.count(); i++)
759         {
760                 if((i == 0) && (parts[i].length() == 2) && parts[i][0].isLetter() && (parts[i][1] == QLatin1Char(':')))
761                 {
762                         continue; //handle case "c:\"
763                 }
764                 parts[i] = MUtils::clean_file_name(parts[i], pretty);
765         }
766
767         const QString cleanPath = parts.join(QLatin1String("/"));
768         return prefix.first.isEmpty() ? cleanPath : prefix.first + cleanPath;
769 }
770
771 ///////////////////////////////////////////////////////////////////////////////
772 // REGULAR EXPESSION HELPER
773 ///////////////////////////////////////////////////////////////////////////////
774
775 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 &value)
776 {
777         return regexp_parse_uint32(regexp, &value, 1U, 1U);
778 }
779
780 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 &value)
781 {
782         return regexp_parse_int32(regexp, &value, 1U, 1U);
783 }
784
785 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 &value, const size_t &offset)
786 {
787         return regexp_parse_uint32(regexp, &value, offset, 1U);
788 }
789
790 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 &value, const size_t &offset)
791 {
792         return regexp_parse_int32(regexp, &value, offset, 1U);
793 }
794
795 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 *values, const size_t &count)
796 {
797         return regexp_parse_uint32(regexp, values, 1U, count);
798 }
799
800 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 *values, const size_t &count)
801 {
802         return regexp_parse_int32(regexp, values, 1U, count);
803 }
804
805 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 *values, const size_t &offset, const size_t &count)
806 {
807         const QStringList caps = regexp.capturedTexts();
808
809         if (caps.isEmpty() || (quint32(caps.count()) <= count))
810         {
811                 return false;
812         }
813
814         for (size_t i = 0; i < count; i++)
815         {
816                 bool ok = false;
817                 values[i] = caps[offset+i].toUInt(&ok);
818                 if (!ok)
819                 {
820                         return false;
821                 }
822         }
823
824         return true;
825 }
826
827 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 *values, const size_t &offset, const size_t &count)
828 {
829         const QStringList caps = regexp.capturedTexts();
830
831         if (caps.isEmpty() || (quint32(caps.count()) <= count))
832         {
833                 return false;
834         }
835
836         for (size_t i = 0; i < count; i++)
837         {
838                 bool ok = false;
839                 values[i] = caps[offset+i].toInt(&ok);
840                 if (!ok)
841                 {
842                         return false;
843                 }
844         }
845
846         return true;
847 }
848
849 ///////////////////////////////////////////////////////////////////////////////
850 // AVAILABLE CODEPAGES
851 ///////////////////////////////////////////////////////////////////////////////
852
853 QStringList MUtils::available_codepages(const bool &noAliases)
854 {
855         QStringList codecList;
856         QList<QByteArray> availableCodecs = QTextCodec::availableCodecs();
857
858         while(!availableCodecs.isEmpty())
859         {
860                 const QByteArray current = availableCodecs.takeFirst();
861                 if(!current.toLower().startsWith("system"))
862                 {
863                         codecList << QString::fromLatin1(current.constData(), current.size());
864                         if(noAliases)
865                         {
866                                 if(QTextCodec *const currentCodec = QTextCodec::codecForName(current.constData()))
867                                 {
868                                         const QList<QByteArray> aliases = currentCodec->aliases();
869                                         for(QList<QByteArray>::ConstIterator iter = aliases.constBegin(); iter != aliases.constEnd(); iter++)
870                                         {
871                                                 availableCodecs.removeAll(*iter);
872                                         }
873                                 }
874                         }
875                 }
876         }
877
878         return codecList;
879 }
880
881 ///////////////////////////////////////////////////////////////////////////////
882 // FP MATH SUPPORT
883 ///////////////////////////////////////////////////////////////////////////////
884
885 MUtils::fp_parts_t MUtils::break_fp(const double value)
886 {
887         fp_parts_t result = { };
888         if (_finite(value))
889         {
890                 result.parts[1] = modf(value, &result.parts[0]);
891         }
892         else
893         {
894                 result.parts[0] = std::numeric_limits<double>::quiet_NaN();
895                 result.parts[1] = std::numeric_limits<double>::quiet_NaN();
896         }
897         return result;
898 }
899
900 ///////////////////////////////////////////////////////////////////////////////
901 // SELF-TEST
902 ///////////////////////////////////////////////////////////////////////////////
903
904 int MUtils::Internal::selfTest(const char *const buildKey, const bool debug)
905 {
906         static const bool MY_DEBUG_FLAG = MUTILS_DEBUG;
907         static const char *const MY_BUILD_KEY = __DATE__ "@" __TIME__;
908
909         if(strncmp(buildKey, MY_BUILD_KEY, 13) || (MY_DEBUG_FLAG != debug))
910         {
911                 MUtils::OS::system_message_err(L"MUtils", L"FATAL ERROR: MUtils library version mismatch detected!");
912                 MUtils::OS::system_message_wrn(L"MUtils", L"Perform a clean(!) re-install of the application to fix the problem!");
913                 abort();
914         }
915         return 0;
916 }