Revamped clean_file_name() function. Do NOT trim *leading* spaces from file name...
[MUtilities.git] / src / Global.cpp
blobbd6646350c3b5450c190db44ec83e083a2a46229
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2016 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.
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
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
22 #if _MSC_VER
23 #define _CRT_RAND_S 1
24 #endif
26 //MUtils
27 #include <MUtils/Global.h>
28 #include <MUtils/OSSupport.h>
30 //Internal
31 #include "DirLocker.h"
32 #include "3rd_party/strnatcmp/include/strnatcmp.h"
34 //Qt
35 #include <QDir>
36 #include <QReadWriteLock>
37 #include <QProcess>
38 #include <QTextCodec>
39 #include <QPair>
40 #include <QListIterator>
42 //CRT
43 #include <cstdlib>
44 #include <ctime>
45 #include <process.h>
47 //VLD
48 #ifdef _MSC_VER
49 #include <vld.h>
50 #endif
52 ///////////////////////////////////////////////////////////////////////////////
53 // Random Support
54 ///////////////////////////////////////////////////////////////////////////////
56 //Robert Jenkins' 96 bit Mix Function
57 static unsigned int mix_function(const unsigned int x, const unsigned int y, const unsigned int z)
59 unsigned int a = x;
60 unsigned int b = y;
61 unsigned int c = z;
63 a=a-b; a=a-c; a=a^(c >> 13);
64 b=b-c; b=b-a; b=b^(a << 8 );
65 c=c-a; c=c-b; c=c^(b >> 13);
66 a=a-b; a=a-c; a=a^(c >> 12);
67 b=b-c; b=b-a; b=b^(a << 16);
68 c=c-a; c=c-b; c=c^(b >> 5 );
69 a=a-b; a=a-c; a=a^(c >> 3 );
70 b=b-c; b=b-a; b=b^(a << 10);
71 c=c-a; c=c-b; c=c^(b >> 15);
73 return a ^ b ^ c;
76 void MUtils::seed_rand(void)
78 qsrand(mix_function(clock(), time(NULL), _getpid()));
81 quint32 MUtils::next_rand32(void)
83 quint32 rnd = 0xDEADBEEF;
85 #ifdef _CRT_RAND_S
86 if(rand_s(&rnd) == 0)
88 return rnd;
90 #endif //_CRT_RAND_S
92 for(size_t i = 0; i < sizeof(quint32); i++)
94 rnd = (rnd << 8) ^ qrand();
97 return rnd;
100 quint64 MUtils::next_rand64(void)
102 return (quint64(next_rand32()) << 32) | quint64(next_rand32());
105 QString MUtils::rand_str(const bool &bLong)
107 if(!bLong)
109 return QString::number(next_rand64(), 16).rightJustified(16, QLatin1Char('0'));
111 return QString("%1%2").arg(rand_str(false), rand_str(false));
114 ///////////////////////////////////////////////////////////////////////////////
115 // GENERATE FILE NAME
116 ///////////////////////////////////////////////////////////////////////////////
118 QString MUtils::make_temp_file(const QString &basePath, const QString &extension, const bool placeholder)
120 for(int i = 0; i < 4096; i++)
122 const QString tempFileName = QString("%1/%2.%3").arg(basePath, rand_str(), extension);
123 if(!QFileInfo(tempFileName).exists())
125 if(placeholder)
127 QFile file(tempFileName);
128 if(file.open(QFile::ReadWrite))
130 file.close();
131 return tempFileName;
134 else
136 return tempFileName;
141 qWarning("Failed to generate temp file name!");
142 return QString();
145 QString MUtils::make_unique_file(const QString &basePath, const QString &baseName, const QString &extension, const bool fancy)
147 quint32 n = fancy ? 2 : 0;
148 QString fileName = fancy ? QString("%1/%2.%3").arg(basePath, baseName, extension) : QString();
149 while (fileName.isEmpty() || QFileInfo(fileName).exists())
151 if (n <= quint32(USHRT_MAX))
153 if (fancy)
155 fileName = QString("%1/%2 (%3).%4").arg(basePath, baseName, QString::number(n++), extension);
157 else
159 fileName = QString("%1/%2.%3.%4").arg(basePath, baseName, QString::number(n++, 16).rightJustified(4, QLatin1Char('0')), extension);
162 else
164 qWarning("Failed to generate unique file name!");
165 return QString();
168 return fileName;
171 ///////////////////////////////////////////////////////////////////////////////
172 // COMPUTE PARITY
173 ///////////////////////////////////////////////////////////////////////////////
176 * Compute parity in parallel
177 * http://www.graphics.stanford.edu/~seander/bithacks.html#ParityParallel
179 bool MUtils::parity(quint32 value)
181 value ^= value >> 16;
182 value ^= value >> 8;
183 value ^= value >> 4;
184 value &= 0xf;
185 return ((0x6996 >> value) & 1) != 0;
188 ///////////////////////////////////////////////////////////////////////////////
189 // TEMP FOLDER
190 ///////////////////////////////////////////////////////////////////////////////
192 static QScopedPointer<MUtils::Internal::DirLock> g_temp_folder_file;
193 static QReadWriteLock g_temp_folder_lock;
195 static QString try_create_subfolder(const QString &baseDir, const QString &postfix)
197 const QString baseDirPath = QDir(baseDir).absolutePath();
198 for(int i = 0; i < 32; i++)
200 QDir directory(baseDirPath);
201 if(directory.mkpath(postfix) && directory.cd(postfix))
203 return directory.canonicalPath();
206 return QString();
209 static MUtils::Internal::DirLock *try_init_temp_folder(const QString &baseDir)
211 const QString tempPath = try_create_subfolder(baseDir, MUtils::rand_str());
212 if(!tempPath.isEmpty())
214 for(int i = 0; i < 32; i++)
216 MUtils::Internal::DirLock *lockFile = NULL;
219 lockFile = new MUtils::Internal::DirLock(tempPath);
220 return lockFile;
222 catch(MUtils::Internal::DirLockException&)
224 /*ignore error and try again*/
228 return NULL;
231 static bool temp_folder_cleanup_helper(const QString &tempPath)
233 size_t delay = 1;
234 static const size_t MAX_DELAY = 8192;
235 forever
237 QDir::setCurrent(QDir::rootPath());
238 if(MUtils::remove_directory(tempPath, true))
240 return true;
242 else
244 if(delay > MAX_DELAY)
246 return false;
248 MUtils::OS::sleep_ms(delay);
249 delay *= 2;
254 static void temp_folder_cleaup(void)
256 QWriteLocker writeLock(&g_temp_folder_lock);
258 //Clean the directory
259 while(!g_temp_folder_file.isNull())
261 const QString tempPath = g_temp_folder_file->getPath();
262 g_temp_folder_file.reset(NULL);
263 if(!temp_folder_cleanup_helper(tempPath))
265 MUtils::OS::system_message_wrn(L"Temp Cleaner", L"Warning: Not all temporary files could be removed!");
270 const QString &MUtils::temp_folder(void)
272 QReadLocker readLock(&g_temp_folder_lock);
274 //Already initialized?
275 if(!g_temp_folder_file.isNull())
277 return g_temp_folder_file->getPath();
280 //Obtain the write lock to initilaize
281 readLock.unlock();
282 QWriteLocker writeLock(&g_temp_folder_lock);
284 //Still uninitilaized?
285 if(!g_temp_folder_file.isNull())
287 return g_temp_folder_file->getPath();
290 //Try the %TMP% or %TEMP% directory first
291 if(MUtils::Internal::DirLock *lockFile = try_init_temp_folder(QDir::tempPath()))
293 g_temp_folder_file.reset(lockFile);
294 atexit(temp_folder_cleaup);
295 return lockFile->getPath();
298 qWarning("%%TEMP%% directory not found -> trying fallback mode now!");
299 static const OS::known_folder_t FOLDER_ID[2] = { OS::FOLDER_LOCALAPPDATA, OS::FOLDER_SYSTROOT_DIR };
300 for(size_t id = 0; id < 2; id++)
302 const QString &knownFolder = OS::known_folder(FOLDER_ID[id]);
303 if(!knownFolder.isEmpty())
305 const QString tempRoot = try_create_subfolder(knownFolder, QLatin1String("TEMP"));
306 if(!tempRoot.isEmpty())
308 if(MUtils::Internal::DirLock *lockFile = try_init_temp_folder(tempRoot))
310 g_temp_folder_file.reset(lockFile);
311 atexit(temp_folder_cleaup);
312 return lockFile->getPath();
318 qFatal("Temporary directory could not be initialized !!!");
319 return (*((QString*)NULL));
322 ///////////////////////////////////////////////////////////////////////////////
323 // REMOVE DIRECTORY / FILE
324 ///////////////////////////////////////////////////////////////////////////////
326 static const QFile::Permissions FILE_PERMISSIONS_NONE = QFile::ReadOther | QFile::WriteOther;
328 bool MUtils::remove_file(const QString &fileName)
330 QFileInfo fileInfo(fileName);
331 if(!(fileInfo.exists() && fileInfo.isFile()))
333 return true;
336 for(int i = 0; i < 32; i++)
338 QFile file(fileName);
339 file.setPermissions(FILE_PERMISSIONS_NONE);
340 if((!(fileInfo.exists() && fileInfo.isFile())) || file.remove())
342 return true;
344 fileInfo.refresh();
347 qWarning("Could not delete \"%s\"", MUTILS_UTF8(fileName));
348 return false;
351 static bool remove_directory_helper(const QDir &folder)
353 if(!folder.exists())
355 return true;
357 const QString dirName = folder.dirName();
358 if(!dirName.isEmpty())
360 QDir parent(folder);
361 if(parent.cdUp())
363 QFile::setPermissions(folder.absolutePath(), FILE_PERMISSIONS_NONE);
364 if(parent.rmdir(dirName))
366 return true;
370 return false;
373 bool MUtils::remove_directory(const QString &folderPath, const bool &recursive)
375 QDir folder(folderPath);
376 if(!folder.exists())
378 return true;
381 if(recursive)
383 const QFileInfoList entryList = folder.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
384 for(QFileInfoList::ConstIterator iter = entryList.constBegin(); iter != entryList.constEnd(); iter++)
386 if(iter->isDir())
388 remove_directory(iter->canonicalFilePath(), true);
390 else if(iter->isFile())
392 remove_file(iter->canonicalFilePath());
397 for(int i = 0; i < 32; i++)
399 if(remove_directory_helper(folder))
401 return true;
403 folder.refresh();
406 qWarning("Could not rmdir \"%s\"", MUTILS_UTF8(folderPath));
407 return false;
410 ///////////////////////////////////////////////////////////////////////////////
411 // PROCESS UTILS
412 ///////////////////////////////////////////////////////////////////////////////
414 static void prependToPath(QProcessEnvironment &env, const QString &value)
416 const QLatin1String PATH = QLatin1String("PATH");
417 const QString path = env.value(PATH, QString()).trimmed();
418 env.insert(PATH, path.isEmpty() ? value : QString("%1;%2").arg(value, path));
421 void MUtils::init_process(QProcess &process, const QString &wokringDir, const bool bReplaceTempDir, const QStringList *const extraPaths)
423 //Environment variable names
424 static const char *const s_envvar_names_temp[] =
426 "TEMP", "TMP", "TMPDIR", "HOME", "USERPROFILE", "HOMEPATH", NULL
428 static const char *const s_envvar_names_remove[] =
430 "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
433 //Initialize environment
434 QProcessEnvironment env = process.processEnvironment();
435 if(env.isEmpty()) env = QProcessEnvironment::systemEnvironment();
437 //Clean a number of enviroment variables that might affect our tools
438 for(size_t i = 0; s_envvar_names_remove[i]; i++)
440 env.remove(QString::fromLatin1(s_envvar_names_remove[i]));
441 env.remove(QString::fromLatin1(s_envvar_names_remove[i]).toLower());
444 const QString tempDir = QDir::toNativeSeparators(temp_folder());
446 //Replace TEMP directory in environment
447 if(bReplaceTempDir)
449 for(size_t i = 0; s_envvar_names_temp[i]; i++)
451 env.insert(s_envvar_names_temp[i], tempDir);
455 //Setup PATH variable
456 prependToPath(env, tempDir);
457 if (extraPaths && (!extraPaths->isEmpty()))
459 QListIterator<QString> iter(*extraPaths);
460 iter.toBack();
461 while (iter.hasPrevious())
463 prependToPath(env, QDir::toNativeSeparators(iter.previous()));
467 //Setup QPorcess object
468 process.setWorkingDirectory(wokringDir);
469 process.setProcessChannelMode(QProcess::MergedChannels);
470 process.setReadChannel(QProcess::StandardOutput);
471 process.setProcessEnvironment(env);
474 ///////////////////////////////////////////////////////////////////////////////
475 // NATURAL ORDER STRING COMPARISON
476 ///////////////////////////////////////////////////////////////////////////////
478 static bool natural_string_sort_helper(const QString &str1, const QString &str2)
480 return (MUtils::Internal::NaturalSort::strnatcmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
483 static bool natural_string_sort_helper_fold_case(const QString &str1, const QString &str2)
485 return (MUtils::Internal::NaturalSort::strnatcasecmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
488 void MUtils::natural_string_sort(QStringList &list, const bool bIgnoreCase)
490 qSort(list.begin(), list.end(), bIgnoreCase ? natural_string_sort_helper_fold_case : natural_string_sort_helper);
493 ///////////////////////////////////////////////////////////////////////////////
494 // CLEAN FILE PATH
495 ///////////////////////////////////////////////////////////////////////////////
497 static const char FILENAME_ILLEGAL_CHARS[] = "\\/:*?<>\"";
499 QString MUtils::clean_file_name(const QString &name)
501 QString result(name);
502 if (result.contains(QLatin1Char('"')))
504 QRegExp quoted("\"(.+)\"");
505 quoted.setMinimal(true);
506 result.replace(quoted, "``\\1ยดยด");
509 for(QString::Iterator iter = result.begin(); iter != result.end(); iter++)
511 if (iter->category() == QChar::Other_Control)
513 *iter = QLatin1Char('_');
517 for(size_t i = 0; FILENAME_ILLEGAL_CHARS[i]; i++)
519 result.replace(QLatin1Char(FILENAME_ILLEGAL_CHARS[i]), QLatin1Char('_'));
522 const QRegExp spaces("\\s+$");
523 result.remove(spaces);
524 while (result.endsWith(QLatin1Char('.')))
526 result.chop(1);
527 result.remove(spaces);
530 return result;
533 static QPair<QString,QString> clean_file_path_get_prefix(const QString path)
535 static const char *const PREFIXES[] =
537 "//?/", "//", "/", NULL
539 const QString posixPath = QDir::fromNativeSeparators(path.trimmed());
540 for (int i = 0; PREFIXES[i]; i++)
542 const QString prefix = QString::fromLatin1(PREFIXES[i]);
543 if (posixPath.startsWith(prefix))
545 return qMakePair(prefix, posixPath.mid(prefix.length()));
548 return qMakePair(QString(), posixPath);
551 QString MUtils::clean_file_path(const QString &path)
553 const QPair<QString, QString> prefix = clean_file_path_get_prefix(path);
555 QStringList parts = prefix.second.split(QLatin1Char('/'), QString::SkipEmptyParts);
556 for(int i = 0; i < parts.count(); i++)
558 if((i == 0) && (parts[i].length() == 2) && parts[i][0].isLetter() && (parts[i][1] == QLatin1Char(':')))
560 continue; //handle case "c:\"
562 parts[i] = MUtils::clean_file_name(parts[i]);
565 const QString cleanPath = parts.join(QLatin1String("/"));
566 return prefix.first.isEmpty() ? cleanPath : prefix.first + cleanPath;
569 ///////////////////////////////////////////////////////////////////////////////
570 // REGULAR EXPESSION HELPER
571 ///////////////////////////////////////////////////////////////////////////////
573 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 &value)
575 return regexp_parse_uint32(regexp, &value, 1);
578 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 *values, const size_t &count)
580 const QStringList caps = regexp.capturedTexts();
582 if(caps.isEmpty() || (quint32(caps.count()) <= count))
584 return false;
587 for(size_t i = 0; i < count; i++)
589 bool ok = false;
590 values[i] = caps[i+1].toUInt(&ok);
591 if(!ok)
593 return false;
597 return true;
600 ///////////////////////////////////////////////////////////////////////////////
601 // AVAILABLE CODEPAGES
602 ///////////////////////////////////////////////////////////////////////////////
604 QStringList MUtils::available_codepages(const bool &noAliases)
606 QStringList codecList;
607 QList<QByteArray> availableCodecs = QTextCodec::availableCodecs();
609 while(!availableCodecs.isEmpty())
611 const QByteArray current = availableCodecs.takeFirst();
612 if(!current.toLower().startsWith("system"))
614 codecList << QString::fromLatin1(current.constData(), current.size());
615 if(noAliases)
617 if(QTextCodec *const currentCodec = QTextCodec::codecForName(current.constData()))
619 const QList<QByteArray> aliases = currentCodec->aliases();
620 for(QList<QByteArray>::ConstIterator iter = aliases.constBegin(); iter != aliases.constEnd(); iter++)
622 availableCodecs.removeAll(*iter);
629 return codecList;
632 ///////////////////////////////////////////////////////////////////////////////
633 // SELF-TEST
634 ///////////////////////////////////////////////////////////////////////////////
636 int MUtils::Internal::selfTest(const char *const buildKey, const bool debug)
638 static const bool MY_DEBUG_FLAG = MUTILS_DEBUG;
639 static const char *const MY_BUILD_KEY = __DATE__ "@" __TIME__;
641 if(strncmp(buildKey, MY_BUILD_KEY, 13) || (MY_DEBUG_FLAG != debug))
643 MUtils::OS::system_message_err(L"MUtils", L"FATAL ERROR: MUtils library version mismatch detected!");
644 MUtils::OS::system_message_wrn(L"MUtils", L"Perform a clean(!) re-install of the application to fix the problem!");
645 abort();
647 return 0;