swift
Loading...
Searching...
No Matches
directoryutils.cpp
1// SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
2// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3
5
7
8#include <algorithm>
9
10#include <QCoreApplication>
11#include <QDateTime>
12#include <QDir>
13#include <QRegularExpression>
14#include <QSet>
15#include <QUrl>
16
17#include "config/buildconfig.h"
18#include "misc/fileutils.h"
19#include "misc/network/ping.h"
20#include "misc/setbuilder.h"
21#include "misc/stringutils.h"
22
23using namespace swift::config;
24using namespace swift::misc::network;
25
26namespace swift::misc
27{
28 bool CDirectoryUtils::isInApplicationDirectory(const QString &path)
29 {
30 if (path.isEmpty())
31 {
32 return false;
33 }
34 return path.contains(qApp->applicationDirPath(), CFileUtils::osFileNameCaseSensitivity());
35 }
36
38 {
39 static const bool appBundle = CBuildConfig::isRunningOnMacOSPlatform() &&
40 qApp->applicationDirPath().contains("Contents/MacOS", Qt::CaseInsensitive);
41 return appBundle;
42 }
43
44 QString CDirectoryUtils::decodeNormalizedDirectory(const QString &directory)
45 {
46 return QUrl::fromPercentEncoding(directory.toUtf8());
47 }
48
49 QStringList CDirectoryUtils::getRelativeSubDirectories(const QString &rootDir)
50 {
51 const QDir dir(rootDir);
52 if (!dir.exists())
53 {
54 return {};
55 }
56 return dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
57 }
58
59 bool CDirectoryUtils::containsFileInDir(const QString &dir, const QString &filter, bool recursively)
60 {
61 const QDir directory(dir);
62 if (!directory.exists())
63 {
64 return false;
65 }
66
67 const QStringList nameFilter({ filter });
68 if (!directory.entryInfoList(nameFilter, QDir::Files | QDir::NoDot | QDir::NoDotDot).isEmpty())
69 {
70 return true;
71 }
72
73 if (!recursively)
74 {
75 return false;
76 }
77 const QStringList relSubDirs = CDirectoryUtils::getRelativeSubDirectories(dir);
78 return std::ranges::any_of(relSubDirs, [&](const QString &relSubDir) {
79 const QString absSubDir = CFileUtils::appendFilePaths(directory.absolutePath(), relSubDir);
80 return CDirectoryUtils::containsFileInDir(absSubDir, filter, recursively);
81 });
82 }
83
84 bool CDirectoryUtils::existsUnemptyDirectory(const QString &testDir)
85 {
86 if (testDir.isEmpty())
87 {
88 return false;
89 }
90 const QDir dir(testDir);
92 {
93 return false;
94 }
95 return !dir.isEmpty();
96 }
97
98 bool CDirectoryUtils::mkPathIfNotExisting(const QString &dir)
99 {
100 const QDir d(dir);
101 if (d.exists())
102 {
103 return true;
104 }
105 QDir mkDir;
106 return mkDir.mkpath(dir);
107 }
108
109 QStringList CDirectoryUtils::getExistingUnemptyDirectories(const QStringList &directories)
110 {
111 QStringList dirs;
112 for (const QString &dir : directories)
113 {
115 {
116 dirs << dir;
117 }
118 }
119 return dirs;
120 }
121
122 bool CDirectoryUtils::isDirExisting(const QString &path)
123 {
124 if (CBuildConfig::isRunningOnWindowsNtPlatform() && CFileUtils::isWindowsUncPath(path))
125 {
126 const QString machine(CFileUtils::windowsUncMachine(path));
127 if (!canPingUncMachine(machine))
128 {
129 return false;
130 } // avoid long "hanging" if machine is switched off
131 }
132 return QDir(path).exists();
133 }
134
135 bool CDirectoryUtils::canPingUncMachine(const QString &machine)
136 {
137 static QMap<QString, qint64> good;
138 static QMap<QString, qint64> bad;
139
140 if (machine.isEmpty())
141 {
142 return false;
143 }
144 const QString m = machine.toLower();
145 if (good.contains(m))
146 {
147 return true;
148 } // good ones we only test once
149 if (bad.contains(m))
150 {
151 const qint64 ts = bad.value(m);
152 const qint64 dt = QDateTime::currentSecsSinceEpoch() - ts;
153 if (dt < 1000 * 60)
154 {
155 return false;
156 } // still bad
157
158 // outdated, test again
159 }
160
161 const bool p = canPing(m);
162 if (p)
163 {
165 bad.remove(m);
166 }
167 else
168 {
170 good.remove(m);
171 }
172 return p;
173 }
174
175 bool CDirectoryUtils::isDirExisting(const QDir &dir)
176 {
178 {
179 return dir.exists();
180 }
181 return CDirectoryUtils::isDirExisting(dir.absolutePath()); // check for UNC
182 }
183
184 bool CDirectoryUtils::isSameExistingDirectory(const QString &dir1, const QString &dir2)
185 {
186 if (dir1.isEmpty() || dir2.isEmpty())
187 {
188 return false;
189 }
190 const QDir d1(dir1);
191 const QDir d2(dir2);
192 if (!d1.exists() || !d2.exists())
193 {
194 return false;
195 }
196 return d1.absolutePath() == d2.absolutePath();
197 }
198
199 bool CDirectoryUtils::isSameOrSubDirectoryOf(const QString &testDir, const QString &dir2)
200 {
201 if (testDir.isEmpty() || dir2.isEmpty())
202 {
203 return false;
204 }
205 const QDir d2(dir2);
206 return CDirectoryUtils::isSameOrSubDirectoryOf(testDir, d2);
207 }
208
209 bool CDirectoryUtils::isSameOrSubDirectoryOf(const QString &dir1, const QDir &parentDir)
210 {
211 QDir d1(dir1);
212 do
213 {
214 if (d1 == parentDir)
215 {
216 return true;
217 }
218 }
219 while (d1.cdUp());
220
221 // not found
222 return false;
223 }
224
225 bool CDirectoryUtils::isSameOrSubDirectoryOfStringBased(const QString &testDir, const QString &parentDir)
226 {
228 const QString td = CFileUtils::fixWindowsUncPath(QDir::cleanPath(testDir));
229 const QString pd = CFileUtils::fixWindowsUncPath(QDir::cleanPath(parentDir));
230 return td.contains(pd, cs);
231 }
232
233 QSet<QString> CDirectoryUtils::fileNamesToQSet(const QFileInfoList &fileInfoList)
234 {
235 CSetBuilder<QString> sl;
236 for (const QFileInfo &info : fileInfoList)
237 {
238 sl.insert(info.fileName());
239 }
240 return sl;
241 }
242
243 QSet<QString> CDirectoryUtils::canonicalFileNamesToQSet(const QFileInfoList &fileInfoList)
244 {
245 CSetBuilder<QString> sl;
246 for (const QFileInfo &info : fileInfoList)
247 {
248 sl.insert(info.canonicalFilePath());
249 }
250 return sl;
251 }
252
253 QSet<QString> CDirectoryUtils::filesToCanonicalNames(const QSet<QString> &fileNames,
254 const QSet<QString> &canonicalFileNames)
255 {
256 CSetBuilder<QString> found;
257 if (fileNames.isEmpty()) return {};
258 for (const QString &canonical : canonicalFileNames)
259 {
260 if (canonical.endsWith('/')) continue;
261 const QString c = canonical.mid(1 + canonical.lastIndexOf('/'));
262 if (fileNames.contains(c))
263 {
264 found.insert(canonical);
265 }
266 }
267 return found;
268 }
269
270 int CDirectoryUtils::copyDirectoryRecursively(const QString &fromDir, const QString &toDir, bool replaceOnConflict)
271 {
272 QDir dir(fromDir);
273 const QStringList fromFiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
274 if (!mkPathIfNotExisting(toDir))
275 {
276 return -1;
277 }
278
279 int count = 0;
280 for (const QString &copyFile : fromFiles)
281 {
282 const QString from = CFileUtils::appendFilePaths(fromDir, copyFile);
283 const QString to = CFileUtils::appendFilePaths(toDir, copyFile);
284 if (QFile::exists(to))
285 {
286 if (!replaceOnConflict)
287 {
288 continue;
289 }
290 if (!QFile::remove(to))
291 {
292 return -1;
293 }
294 }
295 if (!QFile::copy(from, to))
296 {
297 return -1;
298 }
299 count++;
300 }
301
302 const QStringList subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
303 for (const QString &copyDir : subDirs)
304 {
305 const QString fromSubDir = CFileUtils::appendFilePaths(fromDir, copyDir);
306 const QString toSubDir = CFileUtils::appendFilePaths(toDir, copyDir);
307 if (!mkPathIfNotExisting(toDir))
308 {
309 return -1;
310 }
311
312 const int c = copyDirectoryRecursively(fromSubDir, toSubDir, replaceOnConflict);
313 if (c < 0)
314 {
315 return -1;
316 }
317 count += c;
318 }
319 return count;
320 }
321
323 const QString &dirTarget, bool nestedDirs)
324 {
325 DirComparison comp;
326 const QDir d1(dirSource);
327 const QDir d2(dirTarget);
328
329 QFileInfoList dSourceList;
330 QFileInfoList dTargetList;
331 if (d1.exists())
332 {
333 dSourceList = d1.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
334 }
335 if (d2.exists())
336 {
337 dTargetList = d2.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
338 }
339
340 // only names
341 const QSet<QString> sSourceFiles = CDirectoryUtils::fileNamesToQSet(dSourceList);
342 const QSet<QString> sTargetFiles = CDirectoryUtils::fileNamesToQSet(dTargetList);
343
344 // full paths
345 const QSet<QString> sSourceCanonicalFiles = CDirectoryUtils::canonicalFileNamesToQSet(dSourceList);
346 const QSet<QString> sTargetCanonicalFiles = CDirectoryUtils::canonicalFileNamesToQSet(dTargetList);
347
348 QSet<QString> missingInTarget(sSourceFiles);
349 QSet<QString> missingInSource(sTargetFiles);
350 QSet<QString> sameNames(sSourceFiles);
351 missingInSource.subtract(sSourceFiles);
352 missingInTarget.subtract(sTargetFiles);
353 sameNames.intersect(sTargetFiles);
354
355 comp.source = sSourceCanonicalFiles;
356 comp.missingInSource = CDirectoryUtils::filesToCanonicalNames(missingInSource, sSourceCanonicalFiles);
357 comp.missingInTarget = CDirectoryUtils::filesToCanonicalNames(missingInTarget, sSourceCanonicalFiles);
358 comp.sameNameInSource = CDirectoryUtils::filesToCanonicalNames(sameNames, sSourceCanonicalFiles);
359 comp.sameNameInTarget = CDirectoryUtils::filesToCanonicalNames(sameNames, sTargetCanonicalFiles);
360
361 Q_ASSERT_X(comp.sameNameInSource.size() == comp.sameNameInTarget.size(), Q_FUNC_INFO,
362 "Same sets require same size");
363 QSet<QString>::const_iterator targetIt = comp.sameNameInTarget.cbegin();
364 for (const QString &sourceFile : std::as_const(comp.sameNameInSource))
365 {
366 const QFileInfo source(sourceFile);
367 const QFileInfo target(*targetIt);
368 ++targetIt; // avoid cpp check warning
369 if (source.lastModified() == target.lastModified() && source.size() == target.size())
370 {
371 continue;
372 }
373
374 if (source.lastModified() < target.lastModified())
375 {
376 comp.newerInTarget.insert(target.canonicalFilePath());
377 }
378 else
379 {
380 // source.lastModified() >= target.lastModified()
381 comp.newerInSource.insert(source.canonicalFilePath());
382 }
383 }
384
385 if (nestedDirs)
386 {
387 const QStringList relativeSubdirs = CDirectoryUtils::getRelativeSubDirectories(dirSource);
388 if (!relativeSubdirs.isEmpty())
389 {
390 for (const QString &relativeSubdir : relativeSubdirs)
391 {
392 const QString sourceSubdir = CFileUtils::appendFilePaths(dirSource, relativeSubdir);
393 const QString targetSubdir = CFileUtils::appendFilePaths(dirTarget, relativeSubdir);
394 const DirComparison subComparison =
395 CDirectoryUtils::compareTwoDirectories(sourceSubdir, targetSubdir, true);
396 comp.insert(subComparison);
397 }
398 }
399 }
400
401 comp.ok = true;
402 return comp;
403 }
404
406 {
407 source += otherComparison.source;
408 missingInSource += otherComparison.missingInSource;
409 missingInTarget += otherComparison.missingInTarget;
410 newerInSource += otherComparison.newerInSource;
411 newerInTarget += otherComparison.newerInTarget;
412 sameNameInSource += otherComparison.sameNameInSource;
413 sameNameInTarget += otherComparison.sameNameInTarget;
414 }
415} // namespace swift::misc
416
static QStringList getRelativeSubDirectories(const QString &rootDir)
All sub directories of given dir.
static bool mkPathIfNotExisting(const QString &dir)
Make directory if not already existing.
static DirComparison compareTwoDirectories(const QString &dirSource, const QString &dirTarget, bool nestedDirs)
Compare 2 directories (only files, but with hierarchy).
static QString decodeNormalizedDirectory(const QString &directory)
Virtually the inverse operation of CDirectoryUtils::normalizedApplicationDirectory.
static bool isMacOSAppBundle()
Is MacOS application bundle?
static bool isSameOrSubDirectoryOf(const QString &testDir, const QString &parentDir)
Is "testDir" a subdirectory (possibly nested) of "parentDir" or the same directory.
static bool isInApplicationDirectory(const QString &path)
Path in application directory.
static bool containsFileInDir(const QString &dir, const QString &filter, bool recursively)
Any file with filter like "*.txt".
static QStringList getExistingUnemptyDirectories(const QStringList &directories)
Get the existing directories.
static bool isSameOrSubDirectoryOfStringBased(const QString &testDir, const QString &parentDir)
Is "testDir" a subdirectory (possibly nested) of "parentDir" or the same directory.
static bool existsUnemptyDirectory(const QString &testDir)
Exists directory and does it contains files.
static int copyDirectoryRecursively(const QString &fromDir, const QString &toDir, bool replaceOnConflict)
Copy directory recursively.
static bool isSameExistingDirectory(const QString &dir1, const QString &dir2)
Same existing directories.
static bool isDirExisting(const QString &path)
Directory existing? Also checking UNC paths upfront.
static Qt::CaseSensitivity osFileNameCaseSensitivity()
Case sensitivity for current OS.
static QString windowsUncMachine(const QString &filePath)
Machine in Windows UNC path.
static QString appendFilePaths(const QString &path1, const QString &path2)
Append file paths.
static QString fixWindowsUncPath(const QString &filePath)
UNC file paths on Qt start with "/", but UNC file paths only work when they start with "//".
static bool isWindowsUncPath(const QString &filePath)
Windows UNC path?
void insert(const T &value)
Add an element to the set. Runs in amortized constant time.
Definition setbuilder.h:29
Free functions in swift::misc.
qint64 currentSecsSinceEpoch()
QString absolutePath() const const
QString cleanPath(const QString &path)
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
bool mkpath(const QString &dirPath, std::optional< QFileDevice::Permissions > permissions) const const
bool copy(const QString &fileName, const QString &newName)
bool exists() const const
bool remove()
bool isEmpty() const const
bool contains(const Key &key) const const
QMap< Key, T >::iterator insert(QMap< Key, T >::const_iterator pos, const Key &key, const T &value)
QMap< Key, T >::size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
bool contains(const QSet< T > &other) const const
bool isEmpty() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString toLower() const const
QByteArray toUtf8() const const
CaseInsensitive
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
QString fromPercentEncoding(const QByteArray &input)
Result of directory comparison.
QSet< QString > missingInTarget
files not in target, but in source
QSet< QString > sameNameInTarget
file exists in source and target, target name
QSet< QString > source
all source files
QSet< QString > newerInTarget
file in target is newer
void insert(const DirComparison &otherComparison)
Insert values of another comparison.
QSet< QString > newerInSource
file exists in target, but source is newer
QSet< QString > sameNameInSource
file exists in source and target, source name
QSet< QString > missingInSource
files not in source, but in target