Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfileinfogatherer.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include <qcoreapplication.h>
7#include <qdebug.h>
8#include <qdirlisting.h>
9#include <private/qabstractfileiconprovider_p.h>
10#include <private/qfileinfo_p.h>
11#ifndef Q_OS_WIN
12# include <unistd.h>
13# include <sys/types.h>
14#endif
15#if defined(Q_OS_VXWORKS)
16# include "qplatformdefs.h"
17#endif
18
19QT_BEGIN_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23#ifdef QT_BUILD_INTERNAL
24Q_CONSTINIT static QBasicAtomicInt fetchedRoot = Q_BASIC_ATOMIC_INITIALIZER(false);
25Q_AUTOTEST_EXPORT void qt_test_resetFetchedRoot()
26{
27 fetchedRoot.storeRelaxed(false);
28}
29
30Q_AUTOTEST_EXPORT bool qt_test_isFetchedRoot()
31{
32 return fetchedRoot.loadRelaxed();
33}
34#endif
35
36static QString translateDriveName(const QFileInfo &drive)
37{
38 QString driveName = drive.absoluteFilePath();
39#ifdef Q_OS_WIN
40 if (driveName.startsWith(u'/')) // UNC host
41 return drive.fileName();
42 if (driveName.endsWith(u'/'))
43 driveName.chop(1);
44#endif // Q_OS_WIN
45 return driveName;
46}
47
48/*!
49 \class QFileInfoGatherer
50 \inmodule QtGui
51 \internal
52*/
53
54/*!
55 Creates thread
56*/
57QFileInfoGatherer::QFileInfoGatherer(QObject *parent)
58 : QThread(parent)
59 , m_iconProvider(&defaultProvider)
60{
61 start(LowPriority);
62}
63
64/*!
65 Destroys thread
66*/
67QFileInfoGatherer::~QFileInfoGatherer()
68{
69 requestAbort();
70 wait();
71}
72
73bool QFileInfoGatherer::event(QEvent *event)
74{
75 if (event->type() == QEvent::DeferredDelete && isRunning()) {
76 // We have been asked to shut down later but were blocked,
77 // so the owning QFileSystemModel proceeded with its shut-down
78 // and deferred the destruction of the gatherer.
79 // If we are still blocked now, then we have three bad options:
80 // terminate, wait forever (preventing the process from shutting down),
81 // or accept a memory leak.
82 requestAbort();
83 if (!wait(5000)) {
84 // If the application is shutting down, then we terminate.
85 // Otherwise assume that sooner or later the thread will finish,
86 // and we delete it then.
87 if (QCoreApplication::closingDown())
88 terminate();
89 else
90 connect(this, &QThread::finished, this, [this]{ delete this; });
91 return true;
92 }
93 }
94
95 return QThread::event(event);
96}
97
98void QFileInfoGatherer::requestAbort()
99{
100 requestInterruption();
101 QMutexLocker locker(&mutex);
102 condition.wakeAll();
103}
104
105void QFileInfoGatherer::setResolveSymlinks(bool enable)
106{
107 Q_UNUSED(enable);
108#ifdef Q_OS_WIN
109 m_resolveSymlinks = enable;
110#endif
111}
112
113void QFileInfoGatherer::driveAdded()
114{
115 fetchExtendedInformation(QString(), QStringList());
116}
117
118void QFileInfoGatherer::driveRemoved()
119{
120 QStringList drives;
121 const QFileInfoList driveInfoList = QDir::drives();
122 for (const QFileInfo &fi : driveInfoList)
123 drives.append(translateDriveName(fi));
124 emit newListOfFiles(QString(), drives);
125}
126
127bool QFileInfoGatherer::resolveSymlinks() const
128{
129#ifdef Q_OS_WIN
130 return m_resolveSymlinks;
131#else
132 return false;
133#endif
134}
135
136void QFileInfoGatherer::setIconProvider(QAbstractFileIconProvider *provider)
137{
138 m_iconProvider = provider;
139}
140
141QAbstractFileIconProvider *QFileInfoGatherer::iconProvider() const
142{
143 return m_iconProvider;
144}
145
146/*!
147 Fetch extended information for all \a files in \a path
148
149 \sa updateFile(), update(), resolvedName()
150*/
151void QFileInfoGatherer::fetchExtendedInformation(const QString &path, const QStringList &files)
152{
153 QMutexLocker locker(&mutex);
154 // See if we already have this dir/file in our queue
155 qsizetype loc = 0;
156 while ((loc = this->path.lastIndexOf(path, loc - 1)) != -1) {
157 if (this->files.at(loc) == files)
158 return;
159 if (loc == 0)
160 break;
161 }
162
163#if QT_CONFIG(thread)
164 this->path.push(path);
165 this->files.push(files);
166 condition.wakeAll();
167#else // !QT_CONFIG(thread)
168 getFileInfos(path, files);
169#endif // QT_CONFIG(thread)
170
171#if QT_CONFIG(filesystemwatcher)
172 if (files.isEmpty()
173 && !path.isEmpty()
174 && !path.startsWith("//"_L1) /*don't watch UNC path*/) {
175 if (!watchedDirectories().contains(path))
176 watchPaths(QStringList(path));
177 }
178#endif
179}
180
181/*!
182 Fetch extended information for all \a filePath
183
184 \sa fetchExtendedInformation()
185*/
186void QFileInfoGatherer::updateFile(const QString &filePath)
187{
188 QString dir = filePath.mid(0, filePath.lastIndexOf(u'/'));
189 QString fileName = filePath.mid(dir.size() + 1);
190 fetchExtendedInformation(dir, QStringList(fileName));
191}
192
193QStringList QFileInfoGatherer::watchedFiles() const
194{
195#if QT_CONFIG(filesystemwatcher)
196 if (m_watcher)
197 return m_watcher->files();
198#endif
199 return {};
200}
201
202QStringList QFileInfoGatherer::watchedDirectories() const
203{
204#if QT_CONFIG(filesystemwatcher)
205 if (m_watcher)
206 return m_watcher->directories();
207#endif
208 return {};
209}
210
211void QFileInfoGatherer::createWatcher()
212{
213#if QT_CONFIG(filesystemwatcher)
214 m_watcher = new QFileSystemWatcher(this);
215 connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &QFileInfoGatherer::list);
216 connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &QFileInfoGatherer::updateFile);
217# if defined(Q_OS_WIN)
218 const QVariant listener = m_watcher->property("_q_driveListener");
219 if (listener.canConvert<QObject *>()) {
220 if (QObject *driveListener = listener.value<QObject *>()) {
221 connect(driveListener, SIGNAL(driveAdded()), this, SLOT(driveAdded()));
222 connect(driveListener, SIGNAL(driveRemoved()), this, SLOT(driveRemoved()));
223 }
224 }
225# endif // Q_OS_WIN
226#endif
227}
228
229void QFileInfoGatherer::watchPaths(const QStringList &paths)
230{
231#if QT_CONFIG(filesystemwatcher)
232 if (m_watching) {
233 if (m_watcher == nullptr)
234 createWatcher();
235 m_watcher->addPaths(paths);
236 }
237#else
238 Q_UNUSED(paths);
239#endif
240}
241
242void QFileInfoGatherer::unwatchPaths(const QStringList &paths)
243{
244#if QT_CONFIG(filesystemwatcher)
245 if (m_watcher && !paths.isEmpty())
246 m_watcher->removePaths(paths);
247#else
248 Q_UNUSED(paths);
249#endif
250}
251
252bool QFileInfoGatherer::isWatching() const
253{
254 bool result = false;
255#if QT_CONFIG(filesystemwatcher)
256 QMutexLocker locker(&mutex);
257 result = m_watching;
258#endif
259 return result;
260}
261
262/*! \internal
263
264 If \a v is \c false, the QFileSystemWatcher used internally will be deleted
265 and subsequent calls to watchPaths() will do nothing.
266
267 If \a v is \c true, subsequent calls to watchPaths() will add those paths to
268 the filesystem watcher; watchPaths() will initialize a QFileSystemWatcher if
269 one hasn't already been initialized.
270*/
271void QFileInfoGatherer::setWatching(bool v)
272{
273#if QT_CONFIG(filesystemwatcher)
274 QMutexLocker locker(&mutex);
275 if (v != m_watching) {
276 m_watching = v;
277 if (!m_watching)
278 delete std::exchange(m_watcher, nullptr);
279 }
280#else
281 Q_UNUSED(v);
282#endif
283}
284
285/*
286 List all files in \a directoryPath
287
288 \sa listed()
289*/
290void QFileInfoGatherer::clear()
291{
292#if QT_CONFIG(filesystemwatcher)
293 QMutexLocker locker(&mutex);
294 unwatchPaths(watchedFiles());
295 unwatchPaths(watchedDirectories());
296#endif
297}
298
299/*
300 Remove a \a path from the watcher
301
302 \sa listed()
303*/
304void QFileInfoGatherer::removePath(const QString &path)
305{
306#if QT_CONFIG(filesystemwatcher)
307 QMutexLocker locker(&mutex);
308 unwatchPaths(QStringList(path));
309#else
310 Q_UNUSED(path);
311#endif
312}
313
314/*
315 List all files in \a directoryPath
316
317 \sa listed()
318*/
319void QFileInfoGatherer::list(const QString &directoryPath)
320{
321 fetchExtendedInformation(directoryPath, QStringList());
322}
323
324/*
325 Until aborted wait to fetch a directory or files
326*/
327void QFileInfoGatherer::run()
328{
329 forever {
330 // Disallow termination while we are holding a mutex or can be
331 // woken up cleanly.
332 setTerminationEnabled(false);
333 QMutexLocker locker(&mutex);
334 while (!isInterruptionRequested() && path.isEmpty())
335 condition.wait(&mutex);
336 if (isInterruptionRequested())
337 return;
338 const QString thisPath = std::as_const(path).front();
339 path.pop_front();
340 const QStringList thisList = std::as_const(files).front();
341 files.pop_front();
342 locker.unlock();
343
344 // Some of the system APIs we call when gathering file infomration
345 // might hang (e.g. waiting for network), so we explicitly allow
346 // termination now.
347 setTerminationEnabled(true);
348 getFileInfos(thisPath, thisList);
349 }
350}
351
352QExtendedInformation QFileInfoGatherer::getInfo(const QFileInfo &fileInfo) const
353{
354 QExtendedInformation info(fileInfo);
355 if (m_iconProvider) {
356 info.icon = m_iconProvider->icon(fileInfo);
357 info.displayType = m_iconProvider->type(fileInfo);
358 } else {
359 info.displayType = QAbstractFileIconProviderPrivate::getFileType(fileInfo);
360 }
361#if QT_CONFIG(filesystemwatcher)
362 // ### Not ready to listen all modifications by default
363 static const bool watchFiles = qEnvironmentVariableIsSet("QT_FILESYSTEMMODEL_WATCH_FILES");
364 if (watchFiles) {
365 if (!fileInfo.exists() && !fileInfo.isSymLink()) {
366 const_cast<QFileInfoGatherer *>(this)->
367 unwatchPaths(QStringList(fileInfo.absoluteFilePath()));
368 } else {
369 const QString path = fileInfo.absoluteFilePath();
370 if (!path.isEmpty() && fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable()
371 && !watchedFiles().contains(path)) {
372 const_cast<QFileInfoGatherer *>(this)->watchPaths(QStringList(path));
373 }
374 }
375 }
376#endif // filesystemwatcher
377
378#ifdef Q_OS_WIN
379 if (m_resolveSymlinks && info.isSymLink(/* ignoreNtfsSymLinks = */ true)) {
380 QFileInfo resolvedInfo(QFileInfo(fileInfo.symLinkTarget()).canonicalFilePath());
381 if (resolvedInfo.exists()) {
382 emit nameResolved(fileInfo.filePath(), resolvedInfo.fileName());
383 }
384 }
385#endif
386 return info;
387}
388
389/*
390 Get specific file info's, batch the files so update when we have 100
391 items and every 200ms after that
392 */
393void QFileInfoGatherer::getFileInfos(const QString &path, const QStringList &files)
394{
395 // List drives
396 if (path.isEmpty()) {
397#ifdef QT_BUILD_INTERNAL
398 fetchedRoot.storeRelaxed(true);
399#endif
400 QList<std::pair<QString, QFileInfo>> updatedFiles;
401 auto addToUpdatedFiles = [&updatedFiles](QFileInfo &&fileInfo) {
402 fileInfo.stat();
403 updatedFiles.emplace_back(std::pair{translateDriveName(fileInfo), fileInfo});
404 };
405
406 if (files.isEmpty()) {
407 // QDir::drives() calls QFSFileEngine::drives() which creates the QFileInfoList on
408 // the stack and return it, so this list is not shared, so no detaching.
409 QFileInfoList infoList = QDir::drives();
410 updatedFiles.reserve(infoList.size());
411 for (auto rit = infoList.rbegin(), rend = infoList.rend(); rit != rend; ++rit)
412 addToUpdatedFiles(std::move(*rit));
413 } else {
414 updatedFiles.reserve(files.size());
415 for (auto rit = files.crbegin(), rend = files.crend(); rit != rend; ++rit)
416 addToUpdatedFiles(QFileInfo(*rit));
417 }
418 emit updates(path, updatedFiles);
419 return;
420 }
421
422 QElapsedTimer base;
423 base.start();
424 QFileInfo fileInfo;
425 bool firstTime = true;
426 QList<std::pair<QString, QFileInfo>> updatedFiles;
427 QStringList filesToCheck = files;
428
429 QStringList allFiles;
430 if (files.isEmpty()) {
431 using F = QDirListing::IteratorFlag;
432 constexpr auto flags = F::ResolveSymlinks | F::IncludeHidden | F::IncludeDotAndDotDot
433 | F::IncludeBrokenSymlinks;
434 for (const auto &dirEntry : QDirListing(path, flags)) {
435 if (isInterruptionRequested())
436 break;
437 fileInfo = dirEntry.fileInfo();
438 fileInfo.stat();
439 allFiles.append(fileInfo.fileName());
440 fetch(fileInfo, base, firstTime, updatedFiles, path);
441 }
442 }
443 if (!allFiles.isEmpty())
444 emit newListOfFiles(path, allFiles);
445
446 QStringList::const_iterator filesIt = filesToCheck.constBegin();
447 while (!isInterruptionRequested() && filesIt != filesToCheck.constEnd()) {
448 fileInfo.setFile(path + QDir::separator() + *filesIt);
449 ++filesIt;
450 fileInfo.stat();
451 fetch(fileInfo, base, firstTime, updatedFiles, path);
452 }
453 if (!updatedFiles.isEmpty())
454 emit updates(path, updatedFiles);
455 emit directoryLoaded(path);
456}
457
458void QFileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime,
459 QList<std::pair<QString, QFileInfo>> &updatedFiles, const QString &path)
460{
461 updatedFiles.emplace_back(std::pair(fileInfo.fileName(), fileInfo));
462 QElapsedTimer current;
463 current.start();
464 if ((firstTime && updatedFiles.size() > 100) || base.msecsTo(current) > 1000) {
465 emit updates(path, updatedFiles);
466 updatedFiles.clear();
467 base = current;
468 firstTime = false;
469 }
470}
471
472QT_END_NAMESPACE
473
474#include "moc_qfileinfogatherer_p.cpp"
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:177
static QString translateDriveName(const QFileInfo &drive)