8 #include <QCoreApplication>
15 #include <QRegularExpression>
16 #include <QStringBuilder>
17 #include <QTextStream>
24 using namespace swift::config;
28 const QString &CFileUtils::jsonAppendix()
30 static const QString j(
".json");
34 const QString &CFileUtils::jsonWildcardAppendix()
36 static const QString jw(
"*" + jsonAppendix());
40 bool CFileUtils::writeStringToFile(
const QString &content,
const QString &fileNameAndPath)
42 if (fileNameAndPath.isEmpty()) {
return false; }
43 QFile file(fileNameAndPath);
44 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false; }
45 QTextStream stream(&file);
51 bool CFileUtils::writeByteArrayToFile(
const QByteArray &data,
const QString &fileNameAndPath)
53 if (fileNameAndPath.isEmpty()) {
return false; }
54 QFile file(fileNameAndPath);
55 if (!file.open(QIODevice::WriteOnly)) {
return false; }
56 const qint64 c = file.write(data);
58 return c == data.count();
61 bool CFileUtils::writeStringToLockedFile(
const QString &content,
const QString &fileNameAndPath)
63 QLockFile lock(fileNameAndPath +
".lock");
64 if (!lock.lock()) {
return false; }
65 return writeStringToFile(content, fileNameAndPath);
68 QString CFileUtils::readFileToString(
const QString &fileNameAndPath)
70 QFile file(fileNameAndPath);
71 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return {}; }
72 QTextStream stream(&file);
73 const QString content(stream.readAll());
78 QString CFileUtils::readLockedFileToString(
const QString &fileNameAndPath)
80 QLockFile lock(fileNameAndPath +
".lock");
81 if (!lock.lock()) {
return {}; }
82 return readFileToString(fileNameAndPath);
85 QString CFileUtils::readFileToString(
const QString &filePath,
const QString &fileName)
87 return readFileToString(appendFilePaths(filePath, fileName));
90 QString CFileUtils::readLockedFileToString(
const QString &filePath,
const QString &fileName)
92 return readLockedFileToString(appendFilePaths(filePath, fileName));
95 QString CFileUtils::appendFilePaths(
const QString &path1,
const QString &path2)
97 if (path1.isEmpty()) {
return QDir::cleanPath(path2); }
98 if (path2.isEmpty()) {
return QDir::cleanPath(path1); }
99 if (path1.endsWith(
'/'))
102 if (path2.startsWith(
'/')) {
return QDir::cleanPath(path1 % path2.mid(1)); }
103 return QDir::cleanPath(path1 % path2);
105 return QDir::cleanPath(path1 % QChar(
'/') % path2);
108 QString CFileUtils::appendFilePathsAndFixUnc(
const QString &path1,
const QString &path2)
111 return win ? CFileUtils::fixWindowsUncPath(appendFilePaths(path1, path2)) : appendFilePaths(path1, path2);
114 QString CFileUtils::stripFileFromPath(
const QString &path)
116 if (path.endsWith(
'/')) {
return path; }
117 if (!path.contains(
'/')) {
return path; }
118 return path.left(path.lastIndexOf(
'/'));
121 QString CFileUtils::stripFirstSlashPart(
const QString &path)
123 QString p = normalizeFilePathToQtStandard(path);
124 int i = p.indexOf(
'/');
125 if (i < 0) {
return p; }
126 if ((i + 1) >= path.length()) {
return {}; }
127 return path.mid(i + 1);
130 QStringList CFileUtils::stripFirstSlashParts(
const QStringList &paths)
132 QStringList stripped;
133 for (
const QString &path : paths) { stripped.push_back(stripFileFromPath(path)); }
137 QString CFileUtils::stripLeadingSlashOrDriveLetter(
const QString &path)
139 thread_local
const QRegularExpression re(
"^\\/+|^[a-zA-Z]:\\/*");
141 return p.replace(re,
"");
144 QStringList CFileUtils::stripLeadingSlashOrDriveLetters(
const QStringList &paths)
146 QStringList stripped;
147 for (
const QString &path : paths) { stripped.push_back(stripLeadingSlashOrDriveLetter(path)); }
151 QString CFileUtils::lastPathSegment(
const QString &path)
153 if (path.isEmpty()) {
return {}; }
154 if (path.endsWith(
'/')) {
return CFileUtils::lastPathSegment(path.left(path.length() - 1)); }
155 if (!path.contains(
'/')) {
return path; }
156 return path.mid(path.lastIndexOf(
'/') + 1);
159 QString CFileUtils::appendFilePaths(
const QString &path1,
const QString &path2,
const QString &path3)
161 return CFileUtils::appendFilePaths(CFileUtils::appendFilePaths(path1, path2), path3);
164 QString CFileUtils::appendFilePathsAndFixUnc(
const QString &path1,
const QString &path2,
const QString &path3)
167 return win ? CFileUtils::fixWindowsUncPath(
168 CFileUtils::appendFilePaths(CFileUtils::appendFilePaths(path1, path2), path3)) :
169 CFileUtils::appendFilePaths(CFileUtils::appendFilePaths(path1, path2), path3);
172 QString CFileUtils::pathUp(
const QString &path)
174 const int i = path.lastIndexOf(
'/');
175 if (i < 0) {
return path; }
179 QString CFileUtils::normalizeFilePathToQtStandard(
const QString &filePath)
181 if (filePath.isEmpty()) {
return {}; }
182 QString n = QDir::cleanPath(filePath);
183 n = n.replace(
'\\',
'/').replace(
"//",
"/");
187 QStringList CFileUtils::makeDirectoriesRelative(
const QStringList &directories,
const QString &rootDirectory,
188 Qt::CaseSensitivity cs)
191 if (rootDirectory.isEmpty() || rootDirectory ==
"/") {
return directories; }
192 const QString rd(rootDirectory.endsWith(
'/') ? rootDirectory.left(rootDirectory.length() - 1) : rootDirectory);
193 const int p = rd.length();
194 QStringList relativeDirectories;
195 for (
const QString &dir : directories)
197 if (dir.startsWith(rd, cs) && dir.length() > p + 1) { relativeDirectories.append(dir.mid(p + 1)); }
200 relativeDirectories.append(dir);
203 return relativeDirectories;
206 bool CFileUtils::sameDirectories(
const QStringList &dirs1,
const QStringList &dirs2, Qt::CaseSensitivity cs)
209 QStringList dirs1Cleaned(dirs1);
210 QStringList dirs2Cleaned(dirs2);
211 dirs1Cleaned.removeAll(
"");
212 dirs1Cleaned.removeDuplicates();
213 dirs2Cleaned.removeAll(
"");
214 dirs2Cleaned.removeDuplicates();
215 if (dirs1Cleaned.size() != dirs2Cleaned.size()) {
return false; }
218 dirs1Cleaned.sort(cs);
219 dirs2Cleaned.sort(cs);
220 for (
const QString &d1 : dirs1)
227 Qt::CaseSensitivity CFileUtils::osFileNameCaseSensitivity()
232 bool CFileUtils::isFileNameCaseSensitive() {
return CFileUtils::osFileNameCaseSensitivity() == Qt::CaseSensitive; }
234 bool CFileUtils::matchesExcludeDirectory(
const QString &directoryPath,
const QString &excludePattern,
235 Qt::CaseSensitivity cs)
237 if (directoryPath.isEmpty() || excludePattern.isEmpty()) {
return false; }
238 const QString normalizedExcludePattern(normalizeFilePathToQtStandard(excludePattern));
239 return directoryPath.contains(normalizedExcludePattern, cs);
242 bool CFileUtils::isExcludedDirectory(
const QDir &directory,
const QStringList &excludeDirectories,
243 Qt::CaseSensitivity cs)
245 if (excludeDirectories.isEmpty()) {
return false; }
246 const QString d = directory.absolutePath();
247 return isExcludedDirectory(d, excludeDirectories, cs);
250 bool CFileUtils::isExcludedDirectory(
const QFileInfo &fileInfo,
const QStringList &excludeDirectories,
251 Qt::CaseSensitivity cs)
253 if (excludeDirectories.isEmpty()) {
return false; }
254 return isExcludedDirectory(fileInfo.absoluteDir(), excludeDirectories, cs);
257 bool CFileUtils::isExcludedDirectory(
const QString &directoryPath,
const QStringList &excludeDirectories,
258 Qt::CaseSensitivity cs)
260 if (excludeDirectories.isEmpty()) {
return false; }
261 for (
const QString &ex : excludeDirectories)
263 if (matchesExcludeDirectory(directoryPath, ex, cs)) {
return true; }
268 QStringList CFileUtils::removeSubDirectories(
const QStringList &directories, Qt::CaseSensitivity cs)
270 if (directories.size() < 2) {
return directories; }
271 QStringList dirs(directories);
272 dirs.removeDuplicates();
274 if (dirs.size() < 2) {
return dirs; }
278 for (
const QString &path : std::as_const(dirs))
280 if (path.isEmpty()) {
continue; }
281 if (last.isEmpty() || !path.startsWith(last, cs)) { result.append(path); }
287 QString CFileUtils::findFirstExisting(
const QStringList &filesOrDirectory)
289 if (filesOrDirectory.isEmpty()) {
return {}; }
290 for (
const QString &f : filesOrDirectory)
292 if (f.isEmpty()) {
continue; }
293 const QString fn(normalizeFilePathToQtStandard(f));
294 const QFileInfo fi(fn);
295 if (fi.exists()) {
return fi.absoluteFilePath(); }
300 QString CFileUtils::findFirstFile(
const QDir &dir,
bool recursive,
const QStringList &nameFilters,
301 const QStringList &excludeDirectories,
302 std::function<
bool(
const QFileInfo &)> predicate)
304 if (isExcludedDirectory(dir, excludeDirectories)) {
return QString(); }
305 const QFileInfoList result = dir.entryInfoList(nameFilters, QDir::Files);
308 auto it = std::find_if(result.cbegin(), result.cend(), predicate);
309 if (it != result.cend()) {
return it->filePath(); }
313 if (!result.isEmpty()) {
return result.first().filePath(); }
317 for (
const auto &subdir : dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
319 if (isExcludedDirectory(subdir, excludeDirectories)) {
continue; }
320 const QString first =
321 findFirstFile(subdir.filePath(),
true, nameFilters, excludeDirectories, predicate);
322 if (!first.isEmpty()) {
return first; }
328 bool CFileUtils::containsFile(
const QDir &dir,
bool recursive,
const QStringList &nameFilters,
329 const QStringList &excludeDirectories,
330 std::function<
bool(
const QFileInfo &)> predicate)
332 return !findFirstFile(dir, recursive, nameFilters, excludeDirectories, predicate).isEmpty();
335 QString CFileUtils::findFirstNewerThan(
const QDateTime &time,
const QDir &dir,
bool recursive,
336 const QStringList &nameFilters,
const QStringList &excludeDirectories)
338 return findFirstFile(dir, recursive, nameFilters, excludeDirectories,
339 [time](
const QFileInfo &fi) {
return fi.lastModified() > time; });
342 bool CFileUtils::containsFileNewerThan(
const QDateTime &time,
const QDir &dir,
bool recursive,
343 const QStringList &nameFilters,
const QStringList &excludeDirectories)
345 return !findFirstNewerThan(time, dir, recursive, nameFilters, excludeDirectories).isEmpty();
348 QFileInfoList CFileUtils::enumerateFiles(
const QDir &dir,
bool recursive,
const QStringList &nameFilters,
349 const QStringList &excludeDirectories,
350 std::function<
bool(
const QFileInfo &)> predicate)
352 if (isExcludedDirectory(dir, excludeDirectories)) {
return QFileInfoList(); }
353 QFileInfoList result = dir.entryInfoList(nameFilters, QDir::Files);
356 result.erase(std::remove_if(result.begin(), result.end(), [=](
const auto &f) { return !predicate(f); }),
361 for (
const auto &subdir : dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
363 if (isExcludedDirectory(subdir, excludeDirectories)) {
continue; }
364 result += enumerateFiles(subdir.filePath(),
true, nameFilters, excludeDirectories, predicate);
370 QFileInfo CFileUtils::findLastModified(
const QDir &dir,
bool recursive,
const QStringList &nameFilters,
371 const QStringList &excludeDirectories)
373 if (isExcludedDirectory(dir, excludeDirectories)) {
return {}; }
374 const QFileInfoList files = enumerateFiles(dir, recursive, nameFilters, excludeDirectories);
375 if (files.isEmpty()) {
return {}; }
377 auto it = std::max_element(files.cbegin(), files.cend(), [](
const QFileInfo &a,
const QFileInfo &b) {
378 return a.lastModified() < b.lastModified();
383 QFileInfo CFileUtils::findLastCreated(
const QDir &dir,
bool recursive,
const QStringList &nameFilters,
384 const QStringList &excludeDirectories)
386 if (isExcludedDirectory(dir, excludeDirectories)) {
return {}; }
387 const QFileInfoList files = enumerateFiles(dir, recursive, nameFilters, excludeDirectories);
388 if (files.isEmpty()) {
return {}; }
390 auto it = std::max_element(files.cbegin(), files.cend(), [](
const QFileInfo &a,
const QFileInfo &b) {
391 return a.birthTime() < b.birthTime();
396 const QStringList &CFileUtils::getSwiftExecutables()
398 static const QStringList executables(
399 QFileInfo(QCoreApplication::applicationFilePath()).dir().entryList(QDir::Executable | QDir::Files));
403 QStringList CFileUtils::getBaseNamesOnly(
const QStringList &fileNames)
405 QStringList baseNames;
406 for (
const QString &fn : fileNames)
408 const QFileInfo fi(fn);
409 baseNames.push_back(fi.baseName());
414 QStringList CFileUtils::getFileNamesOnly(
const QStringList &fileNames)
417 for (
const QString &fn : fileNames)
419 const QFileInfo fi(fn);
420 fns.push_back(fi.fileName());
425 QString CFileUtils::lockFileError(
const QLockFile &lockFile)
427 switch (lockFile.error())
429 case QLockFile::NoError:
return QStringLiteral(
"No error");
430 case QLockFile::PermissionError:
return QStringLiteral(
"Insufficient permission");
431 case QLockFile::UnknownError:
return QStringLiteral(
"Unknown error");
432 case QLockFile::LockFailedError:
434 QString hostname, appname;
436 lockFile.getLockInfo(&pid, &hostname, &appname);
437 return QStringLiteral(
"Lock open in another process (%1 %2 on %3)")
438 .arg(hostname, QString::number(pid), appname);
440 default:
return QStringLiteral(
"Bad error number");
444 QString CFileUtils::fixWindowsUncPath(
const QString &filePath)
447 if (!win) {
return filePath; }
448 if (!filePath.startsWith(
'/')) {
return filePath; }
449 if (filePath.startsWith(
"//")) {
return filePath; }
450 return QStringLiteral(
"/%1").arg(filePath);
453 QStringList CFileUtils::fixWindowsUncPaths(
const QStringList &filePaths)
456 if (!win) {
return filePaths; }
458 QStringList fixedPaths;
459 for (
const QString &path : filePaths) { fixedPaths << fixWindowsUncPath(path); }
463 bool CFileUtils::isWindowsUncPath(
const QString &filePath)
465 if (filePath.startsWith(
"//") || filePath.startsWith(
"\\\\")) {
return true; }
469 const QString fp = fixWindowsUncPath(filePath);
470 return (fp.startsWith(
"//") || fp.startsWith(
"\\\\"));
473 QString CFileUtils::windowsUncMachine(
const QString &filePath)
475 if (!CFileUtils::isWindowsUncPath(filePath)) {
return {}; }
476 QString f = filePath;
477 f.replace(
"\\",
"/");
479 if (f.startsWith(
"/")) { f = f.mid(1); }
480 const int i = f.indexOf(
'/');
481 if (i < 0) {
return f; }
485 QSet<QString> CFileUtils::windowsUncMachines(
const QSet<QString> &paths)
487 if (paths.isEmpty()) {
return {}; }
489 const Qt::CaseSensitivity cs = osFileNameCaseSensitivity();
490 const bool isCs = isFileNameCaseSensitive();
495 for (
const QString &p : paths)
497 if (!lastMachine.isEmpty() && p.contains(lastMachine, cs))
502 const QString m = isCs ? windowsUncMachine(p) : windowsUncMachine(p).toLower();
503 if (m.isEmpty()) {
continue; }
510 QString CFileUtils::toWindowsLocalPath(
const QString &path)
512 QString p = CFileUtils::fixWindowsUncPath(path);
513 return p.replace(
'/',
'\\');
516 QString CFileUtils::humanReadableFileSize(qint64 size)
520 static const QStringList units({
"KB",
"MB",
"GB",
"TB" });
521 if (size <= 1024) {
return QString::number(size); }
523 QStringListIterator i(units);
524 double currentSize = size;
526 while (currentSize >= 1024.0 && i.hasNext())
529 currentSize /= 1024.0;
531 return QStringLiteral(
"%1 %2").arg(currentSize, 0,
'f', 2).arg(unit);
534 const QStringList &CFileUtils::executableSuffixes()
539 static const QStringList appendixes({
".exe",
".dmg",
".run" });
543 bool CFileUtils::isExecutableFile(
const QString &fileName)
545 for (
const QString &app : CFileUtils::executableSuffixes())
547 if (fileName.endsWith(app, Qt::CaseInsensitive)) {
return true; }
549 return CFileUtils::isSwiftInstaller(fileName);
552 bool CFileUtils::isSwiftInstaller(
const QString &fileName)
554 if (fileName.isEmpty()) {
return false; }
555 return fileName.contains(
"swift", Qt::CaseInsensitive) && fileName.contains(
"installer");
static constexpr bool isRunningOnWindowsNtPlatform()
Running on Windows NT platform?
Build a QSet more efficiently when calling insert() in a for loop.
void insert(const T &value)
Add an element to the set. Runs in amortized constant time.
Free functions in swift::misc.
SWIFT_MISC_EXPORT bool stringCompare(const QString &c1, const QString &c2, Qt::CaseSensitivity cs)
String compare.