swift
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 
6 #include "misc/directoryutils.h"
7 
8 #include <QCoreApplication>
9 #include <QDateTime>
10 #include <QDir>
11 #include <QRegularExpression>
12 #include <QSet>
13 #include <QUrl>
14 
15 #include "config/buildconfig.h"
16 #include "misc/fileutils.h"
17 #include "misc/network/ping.h"
18 #include "misc/setbuilder.h"
19 #include "misc/stringutils.h"
20 
21 using namespace swift::config;
22 using namespace swift::misc::network;
23 
24 namespace swift::misc
25 {
26  bool CDirectoryUtils::isInApplicationDirectory(const QString &path)
27  {
28  if (path.isEmpty()) { return false; }
29  return path.contains(qApp->applicationDirPath(), CFileUtils::osFileNameCaseSensitivity());
30  }
31 
32  bool CDirectoryUtils::isMacOSAppBundle()
33  {
34  static const bool appBundle = CBuildConfig::isRunningOnMacOSPlatform() &&
35  qApp->applicationDirPath().contains("Contents/MacOS", Qt::CaseInsensitive);
36  return appBundle;
37  }
38 
39  QString CDirectoryUtils::decodeNormalizedDirectory(const QString &directory)
40  {
41  return QUrl::fromPercentEncoding(directory.toUtf8());
42  }
43 
44  QStringList CDirectoryUtils::getRelativeSubDirectories(const QString &rootDir)
45  {
46  const QDir dir(rootDir);
47  if (!dir.exists()) { return QStringList(); }
48  return dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
49  }
50 
51  bool CDirectoryUtils::containsFileInDir(const QString &dir, const QString &filter, bool recursively)
52  {
53  const QDir directory(dir);
54  if (!directory.exists()) { return false; }
55 
56  const QStringList nameFilter({ filter });
57  if (!directory.entryInfoList(nameFilter, QDir::Files | QDir::NoDot | QDir::NoDotDot).isEmpty()) { return true; }
58 
59  if (!recursively) { return false; }
60  const QStringList relSubDirs = CDirectoryUtils::getRelativeSubDirectories(dir);
61  for (const QString &relSubDir : relSubDirs)
62  {
63  const QString absSubDir = CFileUtils::appendFilePaths(directory.absolutePath(), relSubDir);
64  if (CDirectoryUtils::containsFileInDir(absSubDir, filter, recursively)) { return true; }
65  }
66  return false;
67  }
68 
69  bool CDirectoryUtils::existsUnemptyDirectory(const QString &testDir)
70  {
71  if (testDir.isEmpty()) { return false; }
72  const QDir dir(testDir);
73  if (!CDirectoryUtils::isDirExisting(dir)) { return false; }
74  return !dir.isEmpty();
75  }
76 
77  bool CDirectoryUtils::mkPathIfNotExisting(const QString &dir)
78  {
79  const QDir d(dir);
80  if (d.exists()) { return true; }
81  QDir mkDir;
82  return mkDir.mkpath(dir);
83  }
84 
85  QStringList CDirectoryUtils::getExistingUnemptyDirectories(const QStringList &directories)
86  {
87  QStringList dirs;
88  for (const QString &dir : directories)
89  {
90  if (CDirectoryUtils::existsUnemptyDirectory(dir)) { dirs << dir; }
91  }
92  return dirs;
93  }
94 
95  bool CDirectoryUtils::isDirExisting(const QString &path)
96  {
97  if (CBuildConfig::isRunningOnWindowsNtPlatform() && CFileUtils::isWindowsUncPath(path))
98  {
99  const QString machine(CFileUtils::windowsUncMachine(path));
100  if (!canPingUncMachine(machine)) { return false; } // avoid long "hanging" if machine is switched off
101  }
102  return QDir(path).exists();
103  }
104 
105  bool CDirectoryUtils::canPingUncMachine(const QString &machine)
106  {
107  static QMap<QString, qint64> good;
108  static QMap<QString, qint64> bad;
109 
110  if (machine.isEmpty()) { return false; }
111  const QString m = machine.toLower();
112  if (good.contains(m)) { return true; } // good ones we only test once
113  if (bad.contains(m))
114  {
115  const qint64 ts = bad.value(m);
116  const qint64 dt = QDateTime::currentSecsSinceEpoch() - ts;
117  if (dt < 1000 * 60) { return false; } // still bad
118 
119  // outdated, test again
120  }
121 
122  const bool p = canPing(m);
123  if (p)
124  {
125  good.insert(m, QDateTime::currentSecsSinceEpoch());
126  bad.remove(m);
127  }
128  else
129  {
130  bad.insert(m, QDateTime::currentSecsSinceEpoch());
131  good.remove(m);
132  }
133  return p;
134  }
135 
136  bool CDirectoryUtils::isDirExisting(const QDir &dir)
137  {
138  if (!CFileUtils::isWindowsUncPath(dir.absolutePath())) { return dir.exists(); }
139  return CDirectoryUtils::isDirExisting(dir.absolutePath()); // check for UNC
140  }
141 
142  bool CDirectoryUtils::isSameExistingDirectory(const QString &dir1, const QString &dir2)
143  {
144  if (dir1.isEmpty() || dir2.isEmpty()) { return false; }
145  const QDir d1(dir1);
146  const QDir d2(dir2);
147  if (!d1.exists() || !d2.exists()) { return false; }
148  return d1.absolutePath() == d2.absolutePath();
149  }
150 
151  bool CDirectoryUtils::isSameOrSubDirectoryOf(const QString &testDir, const QString &dir2)
152  {
153  if (testDir.isEmpty() || dir2.isEmpty()) { return false; }
154  const QDir d2(dir2);
155  return CDirectoryUtils::isSameOrSubDirectoryOf(testDir, d2);
156  }
157 
158  bool CDirectoryUtils::isSameOrSubDirectoryOf(const QString &dir1, const QDir &parentDir)
159  {
160  QDir d1(dir1);
161  do {
162  if (d1 == parentDir) { return true; }
163  }
164  while (d1.cdUp());
165 
166  // not found
167  return false;
168  }
169 
170  bool CDirectoryUtils::isSameOrSubDirectoryOfStringBased(const QString &testDir, const QString &parentDir)
171  {
172  const Qt::CaseSensitivity cs = CFileUtils::osFileNameCaseSensitivity();
173  const QString td = CFileUtils::fixWindowsUncPath(QDir::cleanPath(testDir));
174  const QString pd = CFileUtils::fixWindowsUncPath(QDir::cleanPath(parentDir));
175  return td.contains(pd, cs);
176  }
177 
178  QSet<QString> CDirectoryUtils::fileNamesToQSet(const QFileInfoList &fileInfoList)
179  {
180  CSetBuilder<QString> sl;
181  for (const QFileInfo &info : fileInfoList) { sl.insert(info.fileName()); }
182  return sl;
183  }
184 
185  QSet<QString> CDirectoryUtils::canonicalFileNamesToQSet(const QFileInfoList &fileInfoList)
186  {
187  CSetBuilder<QString> sl;
188  for (const QFileInfo &info : fileInfoList) { sl.insert(info.canonicalFilePath()); }
189  return sl;
190  }
191 
192  QSet<QString> CDirectoryUtils::filesToCanonicalNames(const QSet<QString> &fileNames,
193  const QSet<QString> &canonicalFileNames)
194  {
195  CSetBuilder<QString> found;
196  if (fileNames.isEmpty()) return {};
197  for (const QString &canonical : canonicalFileNames)
198  {
199  if (canonical.endsWith('/')) continue;
200  const QString c = canonical.mid(1 + canonical.lastIndexOf('/'));
201  if (fileNames.contains(c)) { found.insert(canonical); }
202  }
203  return found;
204  }
205 
206  int CDirectoryUtils::copyDirectoryRecursively(const QString &fromDir, const QString &toDir, bool replaceOnConflict)
207  {
208  QDir dir(fromDir);
209  const QStringList fromFiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
210  if (!mkPathIfNotExisting(toDir)) { return -1; }
211 
212  int count = 0;
213  for (const QString &copyFile : fromFiles)
214  {
215  const QString from = CFileUtils::appendFilePaths(fromDir, copyFile);
216  const QString to = CFileUtils::appendFilePaths(toDir, copyFile);
217  if (QFile::exists(to))
218  {
219  if (!replaceOnConflict) { continue; }
220  if (!QFile::remove(to)) { return -1; }
221  }
222  if (!QFile::copy(from, to)) { return -1; }
223  count++;
224  }
225 
226  const QStringList subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
227  for (const QString &copyDir : subDirs)
228  {
229  const QString fromSubDir = CFileUtils::appendFilePaths(fromDir, copyDir);
230  const QString toSubDir = CFileUtils::appendFilePaths(toDir, copyDir);
231  if (!mkPathIfNotExisting(toDir)) { return -1; }
232 
233  const int c = copyDirectoryRecursively(fromSubDir, toSubDir, replaceOnConflict);
234  if (c < 0) { return -1; }
235  count += c;
236  }
237  return count;
238  }
239 
240  CDirectoryUtils::DirComparison CDirectoryUtils::compareTwoDirectories(const QString &dirSource,
241  const QString &dirTarget, bool nestedDirs)
242  {
243  DirComparison comp;
244  const QDir d1(dirSource);
245  const QDir d2(dirTarget);
246 
247  QFileInfoList dSourceList;
248  QFileInfoList dTargetList;
249  if (d1.exists()) { dSourceList = d1.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name); }
250  if (d2.exists()) { dTargetList = d2.entryInfoList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name); }
251 
252  // only names
253  const QSet<QString> sSourceFiles = CDirectoryUtils::fileNamesToQSet(dSourceList);
254  const QSet<QString> sTargetFiles = CDirectoryUtils::fileNamesToQSet(dTargetList);
255 
256  // full paths
257  const QSet<QString> sSourceCanonicalFiles = CDirectoryUtils::canonicalFileNamesToQSet(dSourceList);
258  const QSet<QString> sTargetCanonicalFiles = CDirectoryUtils::canonicalFileNamesToQSet(dTargetList);
259 
260  QSet<QString> missingInTarget(sSourceFiles);
261  QSet<QString> missingInSource(sTargetFiles);
262  QSet<QString> sameNames(sSourceFiles);
263  missingInSource.subtract(sSourceFiles);
264  missingInTarget.subtract(sTargetFiles);
265  sameNames.intersect(sTargetFiles);
266 
267  comp.source = sSourceCanonicalFiles;
268  comp.missingInSource = CDirectoryUtils::filesToCanonicalNames(missingInSource, sSourceCanonicalFiles);
269  comp.missingInTarget = CDirectoryUtils::filesToCanonicalNames(missingInTarget, sSourceCanonicalFiles);
270  comp.sameNameInSource = CDirectoryUtils::filesToCanonicalNames(sameNames, sSourceCanonicalFiles);
271  comp.sameNameInTarget = CDirectoryUtils::filesToCanonicalNames(sameNames, sTargetCanonicalFiles);
272 
273  Q_ASSERT_X(comp.sameNameInSource.size() == comp.sameNameInTarget.size(), Q_FUNC_INFO,
274  "Same sets require same size");
275  QSet<QString>::const_iterator targetIt = comp.sameNameInTarget.cbegin();
276  for (const QString &sourceFile : std::as_const(comp.sameNameInSource))
277  {
278  const QFileInfo source(sourceFile);
279  const QFileInfo target(*targetIt);
280  ++targetIt; // avoid cpp check warning
281  if (source.lastModified() == target.lastModified() && source.size() == target.size()) { continue; }
282 
283  if (source.lastModified() < target.lastModified())
284  {
285  comp.newerInTarget.insert(target.canonicalFilePath());
286  }
287  else
288  {
289  // source.lastModified() >= target.lastModified()
290  comp.newerInSource.insert(source.canonicalFilePath());
291  }
292  }
293 
294  if (nestedDirs)
295  {
296  const QStringList relativeSubdirs = CDirectoryUtils::getRelativeSubDirectories(dirSource);
297  if (!relativeSubdirs.isEmpty())
298  {
299  for (const QString &relativeSubdir : relativeSubdirs)
300  {
301  const QString sourceSubdir = CFileUtils::appendFilePaths(dirSource, relativeSubdir);
302  const QString targetSubdir = CFileUtils::appendFilePaths(dirTarget, relativeSubdir);
303  const DirComparison subComparison =
304  CDirectoryUtils::compareTwoDirectories(sourceSubdir, targetSubdir, true);
305  comp.insert(subComparison);
306  }
307  }
308  }
309 
310  comp.ok = true;
311  return comp;
312  }
313 
314  void CDirectoryUtils::DirComparison::insert(const CDirectoryUtils::DirComparison &otherComparison)
315  {
316  source += otherComparison.source;
317  missingInSource += otherComparison.missingInSource;
318  missingInTarget += otherComparison.missingInTarget;
319  newerInSource += otherComparison.newerInSource;
320  newerInTarget += otherComparison.newerInTarget;
321  sameNameInSource += otherComparison.sameNameInSource;
322  sameNameInTarget += otherComparison.sameNameInTarget;
323  }
324 } // namespace swift::misc
325 
static constexpr bool isRunningOnMacOSPlatform()
Running on MacOS platform?
static constexpr bool isRunningOnWindowsNtPlatform()
Running on Windows NT platform?
Free functions in swift::misc.