OSDN Git Service

Added an overload of next_rand_u32() that generates a random number in [0,N) range.
[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 s_envvar_names_temp[] =
544         {
545                 "TEMP", "TMP", "TMPDIR", "HOME", "USERPROFILE", "HOMEPATH", NULL
546         };
547         static const char *const s_envvar_names_remove[] =
548         {
549                 "WGETRC", "SYSTEM_WGETRC", "HTTP_PROXY", "FTP_PROXY", "NO_PROXY", "GNUPGHOME", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LANG", NULL
550         };
551
552         //Initialize environment
553         QProcessEnvironment env = process.processEnvironment();
554         if(env.isEmpty()) env = QProcessEnvironment::systemEnvironment();
555
556         //Clean a number of enviroment variables that might affect our tools
557         for(size_t i = 0; s_envvar_names_remove[i]; i++)
558         {
559                 env.remove(QString::fromLatin1(s_envvar_names_remove[i]));
560                 env.remove(QString::fromLatin1(s_envvar_names_remove[i]).toLower());
561         }
562
563         const QString tempDir = QDir::toNativeSeparators(temp_folder());
564
565         //Replace TEMP directory in environment
566         if(bReplaceTempDir)
567         {
568                 for(size_t i = 0; s_envvar_names_temp[i]; i++)
569                 {
570                         env.insert(s_envvar_names_temp[i], tempDir);
571                 }
572         }
573
574         //Setup PATH variable
575         prependToPath(env, tempDir);
576         if (extraPaths && (!extraPaths->isEmpty()))
577         {
578                 QListIterator<QString> iter(*extraPaths);
579                 iter.toBack();
580                 while (iter.hasPrevious())
581                 {
582                         prependToPath(env, QDir::toNativeSeparators(iter.previous()));
583                 }
584         }
585         
586         //Setup environment
587         if (extraEnv && (!extraEnv->isEmpty()))
588         {
589                 for (QHash<QString, QString>::ConstIterator iter = extraEnv->constBegin(); iter != extraEnv->constEnd(); iter++)
590                 {
591                         env.insert(iter.key(), iter.value());
592                 }
593         }
594
595         //Setup QPorcess object
596         process.setWorkingDirectory(wokringDir);
597         process.setProcessChannelMode(QProcess::MergedChannels);
598         process.setReadChannel(QProcess::StandardOutput);
599         process.setProcessEnvironment(env);
600 }
601
602 ///////////////////////////////////////////////////////////////////////////////
603 // NATURAL ORDER STRING COMPARISON
604 ///////////////////////////////////////////////////////////////////////////////
605
606 static bool natural_string_sort_helper(const QString &str1, const QString &str2)
607 {
608         return (MUtils::Internal::NaturalSort::strnatcmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
609 }
610
611 static bool natural_string_sort_helper_fold_case(const QString &str1, const QString &str2)
612 {
613         return (MUtils::Internal::NaturalSort::strnatcasecmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
614 }
615
616 void MUtils::natural_string_sort(QStringList &list, const bool bIgnoreCase)
617 {
618         qSort(list.begin(), list.end(), bIgnoreCase ? natural_string_sort_helper_fold_case : natural_string_sort_helper);
619 }
620
621 ///////////////////////////////////////////////////////////////////////////////
622 // CLEAN FILE PATH
623 ///////////////////////////////////////////////////////////////////////////////
624
625 static QMutex                                              g_clean_file_name_mutex;
626 static QScopedPointer<const QList<QPair<QRegExp,QString>>> g_clean_file_name_regex;
627
628 static void clean_file_name_make_pretty(QString &str)
629 {
630         static const struct { const char *p; const char *r; } PATTERN[] =
631         {
632                 { "^\\s*\"([^\"]*)\"\\s*$",    "\\1"                         },  //Remove straight double quotes around the whole string
633                 { "\"([^\"]*)\"",              "\xE2\x80\x9C\\1\xE2\x80\x9D" },  //Replace remaining pairs of straight double quotes with opening/closing double quote
634                 { "^[\\\\/:]+([^\\\\/:]+.*)$", "\\1"                         },  //Remove leading slash, backslash and colon characters
635                 { "^(.*[^\\\\/:]+)[\\\\/:]+$", "\\1"                         },  //Remove trailing slash, backslash and colon characters
636                 { "(\\s*[\\\\/:]\\s*)+",       " - "                         },  //Replace any slash, backslash or colon character that appears in the middle
637                 { NULL, NULL }
638         };
639
640         QMutexLocker locker(&g_clean_file_name_mutex);
641
642         if (g_clean_file_name_regex.isNull())
643         {
644                 QScopedPointer<QList<QPair<QRegExp, QString>>> list(new QList<QPair<QRegExp, QString>>());
645                 for (size_t i = 0; PATTERN[i].p; ++i)
646                 {
647                         list->append(qMakePair(QRegExp(QString::fromUtf8(PATTERN[i].p), Qt::CaseInsensitive), PATTERN[i].r ? QString::fromUtf8(PATTERN[i].r) : QString()));
648                 }
649                 g_clean_file_name_regex.reset(list.take());
650         }
651
652         bool keepOnGoing = !str.isEmpty();
653         while(keepOnGoing)
654         {
655                 const QString prev = str;
656                 keepOnGoing = false;
657                 for (QList<QPair<QRegExp, QString>>::ConstIterator iter = g_clean_file_name_regex->constBegin(); iter != g_clean_file_name_regex->constEnd(); ++iter)
658                 {
659                         str.replace(iter->first, iter->second);
660                         if (str.compare(prev))
661                         {
662                                 str = str.simplified();
663                                 keepOnGoing = !str.isEmpty();
664                                 break;
665                         }
666                 }
667         }
668 }
669
670 QString MUtils::clean_file_name(const QString &name, const bool &pretty)
671 {
672         static const QLatin1Char REPLACEMENT_CHAR('_');
673         static const char FILENAME_ILLEGAL_CHARS[] = "<>:\"/\\|?*";
674         static const char *const FILENAME_RESERVED_NAMES[] =
675         {
676                 "CON", "PRN", "AUX", "NUL",
677                 "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
678                 "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", NULL
679         };
680
681         QString result(name);
682         if (pretty)
683         {
684                 clean_file_name_make_pretty(result);
685         }
686
687         for(QString::Iterator iter = result.begin(); iter != result.end(); iter++)
688         {
689                 if (iter->category() == QChar::Other_Control)
690                 {
691                         *iter = REPLACEMENT_CHAR;
692                 }
693         }
694                 
695         for(size_t i = 0; FILENAME_ILLEGAL_CHARS[i]; i++)
696         {
697                 result.replace(QLatin1Char(FILENAME_ILLEGAL_CHARS[i]), REPLACEMENT_CHAR);
698         }
699         
700         trim_right(result);
701         while (result.endsWith(QLatin1Char('.')))
702         {
703                 result.chop(1);
704                 trim_right(result);
705         }
706
707         for (size_t i = 0; FILENAME_RESERVED_NAMES[i]; i++)
708         {
709                 const QString reserved = QString::fromLatin1(FILENAME_RESERVED_NAMES[i]);
710                 if ((!result.compare(reserved, Qt::CaseInsensitive)) || result.startsWith(reserved + QLatin1Char('.'), Qt::CaseInsensitive))
711                 {
712                         result.replace(0, reserved.length(), QString().leftJustified(reserved.length(), REPLACEMENT_CHAR));
713                 }
714         }
715
716         return result;
717 }
718
719 static QPair<QString,QString> clean_file_path_get_prefix(const QString path)
720 {
721         static const char *const PREFIXES[] =
722         {
723                 "//?/", "//", "/", NULL
724         };
725         const QString posixPath = QDir::fromNativeSeparators(path.trimmed());
726         for (int i = 0; PREFIXES[i]; i++)
727         {
728                 const QString prefix = QString::fromLatin1(PREFIXES[i]);
729                 if (posixPath.startsWith(prefix))
730                 {
731                         return qMakePair(prefix, posixPath.mid(prefix.length()));
732                 }
733         }
734         return qMakePair(QString(), posixPath);
735 }
736
737 QString MUtils::clean_file_path(const QString &path, const bool &pretty)
738 {
739         const QPair<QString, QString> prefix = clean_file_path_get_prefix(path);
740
741         QStringList parts = prefix.second.split(QLatin1Char('/'), QString::SkipEmptyParts);
742         for(int i = 0; i < parts.count(); i++)
743         {
744                 if((i == 0) && (parts[i].length() == 2) && parts[i][0].isLetter() && (parts[i][1] == QLatin1Char(':')))
745                 {
746                         continue; //handle case "c:\"
747                 }
748                 parts[i] = MUtils::clean_file_name(parts[i], pretty);
749         }
750
751         const QString cleanPath = parts.join(QLatin1String("/"));
752         return prefix.first.isEmpty() ? cleanPath : prefix.first + cleanPath;
753 }
754
755 ///////////////////////////////////////////////////////////////////////////////
756 // REGULAR EXPESSION HELPER
757 ///////////////////////////////////////////////////////////////////////////////
758
759 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 &value)
760 {
761         return regexp_parse_uint32(regexp, &value, 1U, 1U);
762 }
763
764 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 &value)
765 {
766         return regexp_parse_int32(regexp, &value, 1U, 1U);
767 }
768
769 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 &value, const size_t &offset)
770 {
771         return regexp_parse_uint32(regexp, &value, offset, 1U);
772 }
773
774 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 &value, const size_t &offset)
775 {
776         return regexp_parse_int32(regexp, &value, offset, 1U);
777 }
778
779 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 *values, const size_t &count)
780 {
781         return regexp_parse_uint32(regexp, values, 1U, count);
782 }
783
784 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 *values, const size_t &count)
785 {
786         return regexp_parse_int32(regexp, values, 1U, count);
787 }
788
789 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 *values, const size_t &offset, const size_t &count)
790 {
791         const QStringList caps = regexp.capturedTexts();
792
793         if (caps.isEmpty() || (quint32(caps.count()) <= count))
794         {
795                 return false;
796         }
797
798         for (size_t i = 0; i < count; i++)
799         {
800                 bool ok = false;
801                 values[i] = caps[offset+i].toUInt(&ok);
802                 if (!ok)
803                 {
804                         return false;
805                 }
806         }
807
808         return true;
809 }
810
811 bool MUtils::regexp_parse_int32(const QRegExp &regexp, qint32 *values, const size_t &offset, const size_t &count)
812 {
813         const QStringList caps = regexp.capturedTexts();
814
815         if (caps.isEmpty() || (quint32(caps.count()) <= count))
816         {
817                 return false;
818         }
819
820         for (size_t i = 0; i < count; i++)
821         {
822                 bool ok = false;
823                 values[i] = caps[offset+i].toInt(&ok);
824                 if (!ok)
825                 {
826                         return false;
827                 }
828         }
829
830         return true;
831 }
832
833 ///////////////////////////////////////////////////////////////////////////////
834 // AVAILABLE CODEPAGES
835 ///////////////////////////////////////////////////////////////////////////////
836
837 QStringList MUtils::available_codepages(const bool &noAliases)
838 {
839         QStringList codecList;
840         QList<QByteArray> availableCodecs = QTextCodec::availableCodecs();
841
842         while(!availableCodecs.isEmpty())
843         {
844                 const QByteArray current = availableCodecs.takeFirst();
845                 if(!current.toLower().startsWith("system"))
846                 {
847                         codecList << QString::fromLatin1(current.constData(), current.size());
848                         if(noAliases)
849                         {
850                                 if(QTextCodec *const currentCodec = QTextCodec::codecForName(current.constData()))
851                                 {
852                                         const QList<QByteArray> aliases = currentCodec->aliases();
853                                         for(QList<QByteArray>::ConstIterator iter = aliases.constBegin(); iter != aliases.constEnd(); iter++)
854                                         {
855                                                 availableCodecs.removeAll(*iter);
856                                         }
857                                 }
858                         }
859                 }
860         }
861
862         return codecList;
863 }
864
865 ///////////////////////////////////////////////////////////////////////////////
866 // FP MATH SUPPORT
867 ///////////////////////////////////////////////////////////////////////////////
868
869 MUtils::fp_parts_t MUtils::break_fp(const double value)
870 {
871         fp_parts_t result = { };
872         if (_finite(value))
873         {
874                 result.parts[1] = modf(value, &result.parts[0]);
875         }
876         else
877         {
878                 result.parts[0] = std::numeric_limits<double>::quiet_NaN();
879                 result.parts[1] = std::numeric_limits<double>::quiet_NaN();
880         }
881         return result;
882 }
883
884 ///////////////////////////////////////////////////////////////////////////////
885 // SELF-TEST
886 ///////////////////////////////////////////////////////////////////////////////
887
888 int MUtils::Internal::selfTest(const char *const buildKey, const bool debug)
889 {
890         static const bool MY_DEBUG_FLAG = MUTILS_DEBUG;
891         static const char *const MY_BUILD_KEY = __DATE__ "@" __TIME__;
892
893         if(strncmp(buildKey, MY_BUILD_KEY, 13) || (MY_DEBUG_FLAG != debug))
894         {
895                 MUtils::OS::system_message_err(L"MUtils", L"FATAL ERROR: MUtils library version mismatch detected!");
896                 MUtils::OS::system_message_wrn(L"MUtils", L"Perform a clean(!) re-install of the application to fix the problem!");
897                 abort();
898         }
899         return 0;
900 }