OSDN Git Service

Added string trimming functions that trim only the left/right side.
[mutilities/MUtilities.git] / src / Global.cpp
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.
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
30 //Internal
31 #include "DirLocker.h"
32 #include "3rd_party/strnatcmp/include/strnatcmp.h"
33
34 //Qt
35 #include <QDir>
36 #include <QReadWriteLock>
37 #include <QProcess>
38 #include <QTextCodec>
39 #include <QPair>
40 #include <QListIterator>
41 #include <QMutex>
42
43 //CRT
44 #include <cstdlib>
45 #include <ctime>
46 #include <process.h>
47
48 //VLD
49 #ifdef _MSC_VER
50 #include <vld.h>
51 #endif
52
53 ///////////////////////////////////////////////////////////////////////////////
54 // Random Support
55 ///////////////////////////////////////////////////////////////////////////////
56
57 //Robert Jenkins' 96 bit Mix Function
58 static unsigned int mix_function(const unsigned int x, const unsigned int y, const unsigned int z)
59 {
60         unsigned int a = x;
61         unsigned int b = y;
62         unsigned int c = z;
63         
64         a=a-b;  a=a-c;  a=a^(c >> 13);
65         b=b-c;  b=b-a;  b=b^(a << 8 ); 
66         c=c-a;  c=c-b;  c=c^(b >> 13);
67         a=a-b;  a=a-c;  a=a^(c >> 12);
68         b=b-c;  b=b-a;  b=b^(a << 16);
69         c=c-a;  c=c-b;  c=c^(b >> 5 );
70         a=a-b;  a=a-c;  a=a^(c >> 3 );
71         b=b-c;  b=b-a;  b=b^(a << 10);
72         c=c-a;  c=c-b;  c=c^(b >> 15);
73
74         return a ^ b ^ c;
75 }
76
77 void MUtils::seed_rand(void)
78 {
79         qsrand(mix_function(clock(), time(NULL), _getpid()));
80 }
81
82 quint32 MUtils::next_rand32(void)
83 {
84         quint32 rnd = 0xDEADBEEF;
85
86 #ifdef _CRT_RAND_S
87         if(rand_s(&rnd) == 0)
88         {
89                 return rnd;
90         }
91 #endif //_CRT_RAND_S
92
93         for(size_t i = 0; i < sizeof(quint32); i++)
94         {
95                 rnd = (rnd << 8) ^ qrand();
96         }
97
98         return rnd;
99 }
100
101 quint64 MUtils::next_rand64(void)
102 {
103         return (quint64(next_rand32()) << 32) | quint64(next_rand32());
104 }
105
106 QString MUtils::rand_str(const bool &bLong)
107 {
108         if(!bLong)
109         {
110                 return QString::number(next_rand64(), 16).rightJustified(16, QLatin1Char('0'));
111         }
112         return QString("%1%2").arg(rand_str(false), rand_str(false));
113 }
114
115 ///////////////////////////////////////////////////////////////////////////////
116 // STRING UTILITY FUNCTIONS
117 ///////////////////////////////////////////////////////////////////////////////
118
119 static QScopedPointer<QRegExp> g_str_trim_rx_r;
120 static QScopedPointer<QRegExp> g_str_trim_rx_l;
121 static QMutex                  g_str_trim_lock;
122
123 static QString& trim_helper(QString &str, QScopedPointer<QRegExp> &regex, const char *const pattern)
124 {
125         QMutexLocker lock(&g_str_trim_lock);
126         if (regex.isNull())
127         {
128                 regex.reset(new QRegExp(QLatin1String(pattern)));
129         }
130         str.remove(*regex.data());
131         return str;
132 }
133
134 QString& MUtils::trim_right(QString &str)
135 {
136         static const char *const TRIM_RIGHT = "\\s+$";
137         return trim_helper(str, g_str_trim_rx_r, TRIM_RIGHT);
138 }
139
140 QString& MUtils::trim_left(QString &str)
141 {
142         static const char *const TRIM_LEFT = "^\\s+";
143         return trim_helper(str, g_str_trim_rx_l, TRIM_LEFT);
144 }
145
146 QString MUtils::trim_right(const QString &str)
147 {
148         QString temp(str);
149         return trim_right(temp);
150 }
151
152 QString MUtils::trim_left(const QString &str)
153 {
154         QString temp(str);
155         return trim_left(temp);
156 }
157
158 ///////////////////////////////////////////////////////////////////////////////
159 // GENERATE FILE NAME
160 ///////////////////////////////////////////////////////////////////////////////
161
162 QString MUtils::make_temp_file(const QString &basePath, const QString &extension, const bool placeholder)
163 {
164         for(int i = 0; i < 4096; i++)
165         {
166                 const QString tempFileName = QString("%1/%2.%3").arg(basePath, rand_str(), extension);
167                 if(!QFileInfo(tempFileName).exists())
168                 {
169                         if(placeholder)
170                         {
171                                 QFile file(tempFileName);
172                                 if(file.open(QFile::ReadWrite))
173                                 {
174                                         file.close();
175                                         return tempFileName;
176                                 }
177                         }
178                         else
179                         {
180                                 return tempFileName;
181                         }
182                 }
183         }
184
185         qWarning("Failed to generate temp file name!");
186         return QString();
187 }
188
189 QString MUtils::make_unique_file(const QString &basePath, const QString &baseName, const QString &extension, const bool fancy)
190 {
191         quint32 n = fancy ? 2 : 0;
192         QString fileName = fancy ? QString("%1/%2.%3").arg(basePath, baseName, extension) : QString();
193         while (fileName.isEmpty() || QFileInfo(fileName).exists())
194         {
195                 if (n <= quint32(USHRT_MAX))
196                 {
197                         if (fancy)
198                         {
199                                 fileName = QString("%1/%2 (%3).%4").arg(basePath, baseName, QString::number(n++), extension);
200                         }
201                         else
202                         {
203                                 fileName = QString("%1/%2.%3.%4").arg(basePath, baseName, QString::number(n++, 16).rightJustified(4, QLatin1Char('0')), extension);
204                         }
205                 }
206                 else
207                 {
208                         qWarning("Failed to generate unique file name!");
209                         return QString();
210                 }
211         }
212         return fileName;
213 }
214
215 ///////////////////////////////////////////////////////////////////////////////
216 // COMPUTE PARITY
217 ///////////////////////////////////////////////////////////////////////////////
218
219 /*
220  * Compute parity in parallel
221  * http://www.graphics.stanford.edu/~seander/bithacks.html#ParityParallel
222  */
223 bool MUtils::parity(quint32 value)
224 {
225         value ^= value >> 16;
226         value ^= value >> 8;
227         value ^= value >> 4;
228         value &= 0xf;
229         return ((0x6996 >> value) & 1) != 0;
230 }
231
232 ///////////////////////////////////////////////////////////////////////////////
233 // TEMP FOLDER
234 ///////////////////////////////////////////////////////////////////////////////
235
236 static QScopedPointer<MUtils::Internal::DirLock> g_temp_folder_file;
237 static QReadWriteLock                            g_temp_folder_lock;
238
239 static QString try_create_subfolder(const QString &baseDir, const QString &postfix)
240 {
241         const QString baseDirPath = QDir(baseDir).absolutePath();
242         for(int i = 0; i < 32; i++)
243         {
244                 QDir directory(baseDirPath);
245                 if(directory.mkpath(postfix) && directory.cd(postfix))
246                 {
247                         return directory.canonicalPath();
248                 }
249         }
250         return QString();
251 }
252
253 static MUtils::Internal::DirLock *try_init_temp_folder(const QString &baseDir)
254 {
255         const QString tempPath = try_create_subfolder(baseDir, MUtils::rand_str());
256         if(!tempPath.isEmpty())
257         {
258                 for(int i = 0; i < 32; i++)
259                 {
260                         MUtils::Internal::DirLock *lockFile = NULL;
261                         try
262                         {
263                                 lockFile = new MUtils::Internal::DirLock(tempPath);
264                                 return lockFile;
265                         }
266                         catch(MUtils::Internal::DirLockException&)
267                         {
268                                 /*ignore error and try again*/
269                         }
270                 }
271         }
272         return NULL;
273 }
274
275 static bool temp_folder_cleanup_helper(const QString &tempPath)
276 {
277         size_t delay = 1;
278         static const size_t MAX_DELAY = 8192;
279         forever
280         {
281                 QDir::setCurrent(QDir::rootPath());
282                 if(MUtils::remove_directory(tempPath, true))
283                 {
284                         return true;
285                 }
286                 else
287                 {
288                         if(delay > MAX_DELAY)
289                         {
290                                 return false;
291                         }
292                         MUtils::OS::sleep_ms(delay);
293                         delay *= 2;
294                 }
295         }
296 }
297
298 static void temp_folder_cleaup(void)
299 {
300         QWriteLocker writeLock(&g_temp_folder_lock);
301
302         //Clean the directory
303         while(!g_temp_folder_file.isNull())
304         {
305                 const QString tempPath = g_temp_folder_file->getPath();
306                 g_temp_folder_file.reset(NULL);
307                 if(!temp_folder_cleanup_helper(tempPath))
308                 {
309                         MUtils::OS::system_message_wrn(L"Temp Cleaner", L"Warning: Not all temporary files could be removed!");
310                 }
311         }
312 }
313
314 const QString &MUtils::temp_folder(void)
315 {
316         QReadLocker readLock(&g_temp_folder_lock);
317
318         //Already initialized?
319         if(!g_temp_folder_file.isNull())
320         {
321                 return g_temp_folder_file->getPath();
322         }
323
324         //Obtain the write lock to initilaize
325         readLock.unlock();
326         QWriteLocker writeLock(&g_temp_folder_lock);
327         
328         //Still uninitilaized?
329         if(!g_temp_folder_file.isNull())
330         {
331                 return g_temp_folder_file->getPath();
332         }
333
334         //Try the %TMP% or %TEMP% directory first
335         if(MUtils::Internal::DirLock *lockFile = try_init_temp_folder(QDir::tempPath()))
336         {
337                 g_temp_folder_file.reset(lockFile);
338                 atexit(temp_folder_cleaup);
339                 return lockFile->getPath();
340         }
341
342         qWarning("%%TEMP%% directory not found -> trying fallback mode now!");
343         static const OS::known_folder_t FOLDER_ID[2] = { OS::FOLDER_LOCALAPPDATA, OS::FOLDER_SYSTROOT_DIR };
344         for(size_t id = 0; id < 2; id++)
345         {
346                 const QString &knownFolder = OS::known_folder(FOLDER_ID[id]);
347                 if(!knownFolder.isEmpty())
348                 {
349                         const QString tempRoot = try_create_subfolder(knownFolder, QLatin1String("TEMP"));
350                         if(!tempRoot.isEmpty())
351                         {
352                                 if(MUtils::Internal::DirLock *lockFile = try_init_temp_folder(tempRoot))
353                                 {
354                                         g_temp_folder_file.reset(lockFile);
355                                         atexit(temp_folder_cleaup);
356                                         return lockFile->getPath();
357                                 }
358                         }
359                 }
360         }
361
362         qFatal("Temporary directory could not be initialized !!!");
363         return (*((QString*)NULL));
364 }
365
366 ///////////////////////////////////////////////////////////////////////////////
367 // REMOVE DIRECTORY / FILE
368 ///////////////////////////////////////////////////////////////////////////////
369
370 static const QFile::Permissions FILE_PERMISSIONS_NONE = QFile::ReadOther | QFile::WriteOther;
371
372 bool MUtils::remove_file(const QString &fileName)
373 {
374         QFileInfo fileInfo(fileName);
375         if(!(fileInfo.exists() && fileInfo.isFile()))
376         {
377                 return true;
378         }
379
380         for(int i = 0; i < 32; i++)
381         {
382                 QFile file(fileName);
383                 file.setPermissions(FILE_PERMISSIONS_NONE);
384                 if((!(fileInfo.exists() && fileInfo.isFile())) || file.remove())
385                 {
386                         return true;
387                 }
388                 fileInfo.refresh();
389         }
390
391         qWarning("Could not delete \"%s\"", MUTILS_UTF8(fileName));
392         return false;
393 }
394
395 static bool remove_directory_helper(const QDir &folder)
396 {
397         if(!folder.exists())
398         {
399                 return true;
400         }
401         const QString dirName = folder.dirName();
402         if(!dirName.isEmpty())
403         {
404                 QDir parent(folder);
405                 if(parent.cdUp())
406                 {
407                         QFile::setPermissions(folder.absolutePath(), FILE_PERMISSIONS_NONE);
408                         if(parent.rmdir(dirName))
409                         {
410                                 return true;
411                         }
412                 }
413         }
414         return false;
415 }
416
417 bool MUtils::remove_directory(const QString &folderPath, const bool &recursive)
418 {
419         QDir folder(folderPath);
420         if(!folder.exists())
421         {
422                 return true;
423         }
424
425         if(recursive)
426         {
427                 const QFileInfoList entryList = folder.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden);
428                 for(QFileInfoList::ConstIterator iter = entryList.constBegin(); iter != entryList.constEnd(); iter++)
429                 {
430                         if(iter->isDir())
431                         {
432                                 remove_directory(iter->canonicalFilePath(), true);
433                         }
434                         else if(iter->isFile())
435                         {
436                                 remove_file(iter->canonicalFilePath());
437                         }
438                 }
439         }
440
441         for(int i = 0; i < 32; i++)
442         {
443                 if(remove_directory_helper(folder))
444                 {
445                         return true;
446                 }
447                 folder.refresh();
448         }
449         
450         qWarning("Could not rmdir \"%s\"", MUTILS_UTF8(folderPath));
451         return false;
452 }
453
454 ///////////////////////////////////////////////////////////////////////////////
455 // PROCESS UTILS
456 ///////////////////////////////////////////////////////////////////////////////
457
458 static void prependToPath(QProcessEnvironment &env, const QString &value)
459 {
460         const QLatin1String PATH = QLatin1String("PATH");
461         const QString path = env.value(PATH, QString()).trimmed();
462         env.insert(PATH, path.isEmpty() ? value : QString("%1;%2").arg(value, path));
463 }
464
465 void MUtils::init_process(QProcess &process, const QString &wokringDir, const bool bReplaceTempDir, const QStringList *const extraPaths)
466 {
467         //Environment variable names
468         static const char *const s_envvar_names_temp[] =
469         {
470                 "TEMP", "TMP", "TMPDIR", "HOME", "USERPROFILE", "HOMEPATH", NULL
471         };
472         static const char *const s_envvar_names_remove[] =
473         {
474                 "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
475         };
476
477         //Initialize environment
478         QProcessEnvironment env = process.processEnvironment();
479         if(env.isEmpty()) env = QProcessEnvironment::systemEnvironment();
480
481         //Clean a number of enviroment variables that might affect our tools
482         for(size_t i = 0; s_envvar_names_remove[i]; i++)
483         {
484                 env.remove(QString::fromLatin1(s_envvar_names_remove[i]));
485                 env.remove(QString::fromLatin1(s_envvar_names_remove[i]).toLower());
486         }
487
488         const QString tempDir = QDir::toNativeSeparators(temp_folder());
489
490         //Replace TEMP directory in environment
491         if(bReplaceTempDir)
492         {
493                 for(size_t i = 0; s_envvar_names_temp[i]; i++)
494                 {
495                         env.insert(s_envvar_names_temp[i], tempDir);
496                 }
497         }
498
499         //Setup PATH variable
500         prependToPath(env, tempDir);
501         if (extraPaths && (!extraPaths->isEmpty()))
502         {
503                 QListIterator<QString> iter(*extraPaths);
504                 iter.toBack();
505                 while (iter.hasPrevious())
506                 {
507                         prependToPath(env, QDir::toNativeSeparators(iter.previous()));
508                 }
509         }
510         
511         //Setup QPorcess object
512         process.setWorkingDirectory(wokringDir);
513         process.setProcessChannelMode(QProcess::MergedChannels);
514         process.setReadChannel(QProcess::StandardOutput);
515         process.setProcessEnvironment(env);
516 }
517
518 ///////////////////////////////////////////////////////////////////////////////
519 // NATURAL ORDER STRING COMPARISON
520 ///////////////////////////////////////////////////////////////////////////////
521
522 static bool natural_string_sort_helper(const QString &str1, const QString &str2)
523 {
524         return (MUtils::Internal::NaturalSort::strnatcmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
525 }
526
527 static bool natural_string_sort_helper_fold_case(const QString &str1, const QString &str2)
528 {
529         return (MUtils::Internal::NaturalSort::strnatcasecmp(MUTILS_WCHR(str1), MUTILS_WCHR(str2)) < 0);
530 }
531
532 void MUtils::natural_string_sort(QStringList &list, const bool bIgnoreCase)
533 {
534         qSort(list.begin(), list.end(), bIgnoreCase ? natural_string_sort_helper_fold_case : natural_string_sort_helper);
535 }
536
537 ///////////////////////////////////////////////////////////////////////////////
538 // CLEAN FILE PATH
539 ///////////////////////////////////////////////////////////////////////////////
540
541 QString MUtils::clean_file_name(const QString &name)
542 {
543         static const char FILENAME_ILLEGAL_CHARS[] = "\\/:*?<>\"";
544
545         QString result(name);
546         if (result.contains(QLatin1Char('"')))
547         {
548                 QRegExp quoted("\"(.+)\"");
549                 quoted.setMinimal(true);
550                 result.replace(quoted, "``\\1ยดยด");
551         }
552
553         for(QString::Iterator iter = result.begin(); iter != result.end(); iter++)
554         {
555                 if (iter->category() == QChar::Other_Control)
556                 {
557                         *iter = QLatin1Char('_');
558                 }
559         }
560                 
561         for(size_t i = 0; FILENAME_ILLEGAL_CHARS[i]; i++)
562         {
563                 result.replace(QLatin1Char(FILENAME_ILLEGAL_CHARS[i]), QLatin1Char('_'));
564         }
565         
566         trim_right(result);
567         while (result.endsWith(QLatin1Char('.')))
568         {
569                 result.chop(1);
570                 trim_right(result);
571         }
572
573         return result;
574 }
575
576 static QPair<QString,QString> clean_file_path_get_prefix(const QString path)
577 {
578         static const char *const PREFIXES[] =
579         {
580                 "//?/", "//", "/", NULL
581         };
582         const QString posixPath = QDir::fromNativeSeparators(path.trimmed());
583         for (int i = 0; PREFIXES[i]; i++)
584         {
585                 const QString prefix = QString::fromLatin1(PREFIXES[i]);
586                 if (posixPath.startsWith(prefix))
587                 {
588                         return qMakePair(prefix, posixPath.mid(prefix.length()));
589                 }
590         }
591         return qMakePair(QString(), posixPath);
592 }
593
594 QString MUtils::clean_file_path(const QString &path)
595 {
596         const QPair<QString, QString> prefix = clean_file_path_get_prefix(path);
597
598         QStringList parts = prefix.second.split(QLatin1Char('/'), QString::SkipEmptyParts);
599         for(int i = 0; i < parts.count(); i++)
600         {
601                 if((i == 0) && (parts[i].length() == 2) && parts[i][0].isLetter() && (parts[i][1] == QLatin1Char(':')))
602                 {
603                         continue; //handle case "c:\"
604                 }
605                 parts[i] = MUtils::clean_file_name(parts[i]);
606         }
607
608         const QString cleanPath = parts.join(QLatin1String("/"));
609         return prefix.first.isEmpty() ? cleanPath : prefix.first + cleanPath;
610 }
611
612 ///////////////////////////////////////////////////////////////////////////////
613 // REGULAR EXPESSION HELPER
614 ///////////////////////////////////////////////////////////////////////////////
615
616 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 &value)
617 {
618         return regexp_parse_uint32(regexp, &value, 1);
619 }
620
621 bool MUtils::regexp_parse_uint32(const QRegExp &regexp, quint32 *values, const size_t &count)
622 {
623         const QStringList caps = regexp.capturedTexts();
624         
625         if(caps.isEmpty() || (quint32(caps.count()) <= count))
626         {
627                 return false;
628         }
629
630         for(size_t i = 0; i < count; i++)
631         {
632                 bool ok = false;
633                 values[i] = caps[i+1].toUInt(&ok);
634                 if(!ok)
635                 {
636                         return false;
637                 }
638         }
639
640         return true;
641 }
642
643 ///////////////////////////////////////////////////////////////////////////////
644 // AVAILABLE CODEPAGES
645 ///////////////////////////////////////////////////////////////////////////////
646
647 QStringList MUtils::available_codepages(const bool &noAliases)
648 {
649         QStringList codecList;
650         QList<QByteArray> availableCodecs = QTextCodec::availableCodecs();
651
652         while(!availableCodecs.isEmpty())
653         {
654                 const QByteArray current = availableCodecs.takeFirst();
655                 if(!current.toLower().startsWith("system"))
656                 {
657                         codecList << QString::fromLatin1(current.constData(), current.size());
658                         if(noAliases)
659                         {
660                                 if(QTextCodec *const currentCodec = QTextCodec::codecForName(current.constData()))
661                                 {
662                                         const QList<QByteArray> aliases = currentCodec->aliases();
663                                         for(QList<QByteArray>::ConstIterator iter = aliases.constBegin(); iter != aliases.constEnd(); iter++)
664                                         {
665                                                 availableCodecs.removeAll(*iter);
666                                         }
667                                 }
668                         }
669                 }
670         }
671
672         return codecList;
673 }
674
675 ///////////////////////////////////////////////////////////////////////////////
676 // SELF-TEST
677 ///////////////////////////////////////////////////////////////////////////////
678
679 int MUtils::Internal::selfTest(const char *const buildKey, const bool debug)
680 {
681         static const bool MY_DEBUG_FLAG = MUTILS_DEBUG;
682         static const char *const MY_BUILD_KEY = __DATE__ "@" __TIME__;
683
684         if(strncmp(buildKey, MY_BUILD_KEY, 13) || (MY_DEBUG_FLAG != debug))
685         {
686                 MUtils::OS::system_message_err(L"MUtils", L"FATAL ERROR: MUtils library version mismatch detected!");
687                 MUtils::OS::system_message_wrn(L"MUtils", L"Perform a clean(!) re-install of the application to fix the problem!");
688                 abort();
689         }
690         return 0;
691 }