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