File indexing completed on 2024-05-19 04:56:02
0001 /** 0002 * \file fileinfogatherer.cpp 0003 * \cond 0004 * Taken from Qt Git, revision e73bd4a 0005 * qtbase/src/widgets/dialogs/qfileinfogatherer.cpp 0006 * Adapted for Kid3 with the following changes: 0007 * - Remove Q prefix from class names 0008 * - Remove QT_..._CONFIG, QT_..._NAMESPACE, Q_..._EXPORT... 0009 * - Allow compilation with Qt versions < 5.7 0010 * - Remove moc includes 0011 * - Remove dependencies to Qt5::Widgets 0012 */ 0013 /**************************************************************************** 0014 ** 0015 ** Copyright (C) 2016 The Qt Company Ltd. 0016 ** Contact: https://www.qt.io/licensing/ 0017 ** 0018 ** This file is part of the QtWidgets module of the Qt Toolkit. 0019 ** 0020 ** $QT_BEGIN_LICENSE:LGPL$ 0021 ** Commercial License Usage 0022 ** Licensees holding valid commercial Qt licenses may use this file in 0023 ** accordance with the commercial license agreement provided with the 0024 ** Software or, alternatively, in accordance with the terms contained in 0025 ** a written agreement between you and The Qt Company. For licensing terms 0026 ** and conditions see https://www.qt.io/terms-conditions. For further 0027 ** information use the contact form at https://www.qt.io/contact-us. 0028 ** 0029 ** GNU Lesser General Public License Usage 0030 ** Alternatively, this file may be used under the terms of the GNU Lesser 0031 ** General Public License version 3 as published by the Free Software 0032 ** Foundation and appearing in the file LICENSE.LGPL3 included in the 0033 ** packaging of this file. Please review the following information to 0034 ** ensure the GNU Lesser General Public License version 3 requirements 0035 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 0036 ** 0037 ** GNU General Public License Usage 0038 ** Alternatively, this file may be used under the terms of the GNU 0039 ** General Public License version 2.0 or (at your option) the GNU General 0040 ** Public license version 3 or any later version approved by the KDE Free 0041 ** Qt Foundation. The licenses are as published by the Free Software 0042 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 0043 ** included in the packaging of this file. Please review the following 0044 ** information to ensure the GNU General Public License requirements will 0045 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and 0046 ** https://www.gnu.org/licenses/gpl-3.0.html. 0047 ** 0048 ** $QT_END_LICENSE$ 0049 ** 0050 ****************************************************************************/ 0051 0052 #include "fileinfogatherer_p.h" 0053 #include <qdebug.h> 0054 #include <qdiriterator.h> 0055 #ifndef Q_OS_WIN 0056 # include <unistd.h> 0057 # include <sys/types.h> 0058 #endif 0059 #if defined(Q_OS_VXWORKS) 0060 # include "qplatformdefs.h" 0061 #endif 0062 #include "abstractfiledecorationprovider.h" 0063 0064 #ifdef Q_OS_WIN 0065 #include <windows.h> 0066 0067 /** 0068 * Check if path is a drive which could cause an insert disk dialog to pop up. 0069 * 0070 * This method should be used before calling QFileInfo::permissions(), 0071 * or QFileInfo::isReadable() on Windows. 0072 * The bug has been reported for Windows 7 32-bit and could be reproduced with 0073 * Windows XP. To trigger the bug, a CD has to be inserted and then removed once 0074 * before fetching the root directory with a file system model. See 0075 * https://forum.qt.io/topic/34799/checking-is-a-drive-is-readable-in-qt-pops-up-a-no-disk-error-in-windows-7 0076 * 0077 * @param path drive path, e.g. "D:/" 0078 * @return true if path is for a drive and getting volume information fails. 0079 */ 0080 bool ExtendedInformation::isInvalidDrive(const QString &path) 0081 { 0082 // Windows drive nodes are queried with paths like "D:/", check if path is 0083 // a drive letter followed by a colon. 0084 const int pathLen = path.length(); 0085 if (pathLen < 2 || pathLen > 3 || path.at(1) != QLatin1Char(':') || 0086 !path.at(0).isLetter()) 0087 return false; 0088 0089 const DWORD VOLUME_NAME_SIZE = 255; 0090 const DWORD FILE_SYSTEM_NAME_SIZE = 255; 0091 LPCWSTR rootPathName = (LPCWSTR)path.utf16(); 0092 UCHAR fileSystemNameBuffer[255], volumeNameBuffer[255]; 0093 DWORD volumeSerialNumber, maximumComponentLength, fileSystemFlags; 0094 0095 BOOL bSuccess = ::GetVolumeInformationW( 0096 rootPathName, 0097 (LPWSTR)volumeNameBuffer, 0098 VOLUME_NAME_SIZE, 0099 &volumeSerialNumber, 0100 &maximumComponentLength, 0101 &fileSystemFlags, 0102 (LPWSTR)fileSystemNameBuffer, 0103 FILE_SYSTEM_NAME_SIZE 0104 ); 0105 0106 return !bSuccess; 0107 } 0108 #endif // Q_OS_WIN 0109 0110 #ifdef QT_BUILD_INTERNAL 0111 static QBasicAtomicInt fetchedRoot = Q_BASIC_ATOMIC_INITIALIZER(false); 0112 void qt_test_resetFetchedRoot() 0113 { 0114 fetchedRoot.store(false); 0115 } 0116 0117 bool qt_test_isFetchedRoot() 0118 { 0119 return fetchedRoot.load(); 0120 } 0121 #endif 0122 0123 static QString translateDriveName(const QFileInfo &drive) 0124 { 0125 QString driveName = drive.absoluteFilePath(); 0126 #ifdef Q_OS_WIN 0127 if (driveName.startsWith(QLatin1Char('/'))) // UNC host 0128 return drive.fileName(); 0129 if (driveName.endsWith(QLatin1Char('/'))) 0130 driveName.chop(1); 0131 #endif // Q_OS_WIN 0132 return driveName; 0133 } 0134 0135 /*! 0136 Creates thread 0137 */ 0138 FileInfoGatherer::FileInfoGatherer(QObject *parent) 0139 : QThread(parent), abort(false), 0140 #ifndef QT_NO_FILESYSTEMWATCHER 0141 watcher(nullptr), 0142 #endif 0143 #ifdef Q_OS_WIN 0144 m_resolveSymlinks(true), 0145 #endif 0146 m_decorationProvider(Q_NULLPTR) 0147 { 0148 #ifndef QT_NO_FILESYSTEMWATCHER 0149 watcher = new QFileSystemWatcher(this); 0150 connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(list(QString))); 0151 connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(updateFile(QString))); 0152 0153 # if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) 0154 const QVariant listener = watcher->property("_q_driveListener"); 0155 if (listener.canConvert<QObject *>()) { 0156 if (QObject *driveListener = listener.value<QObject *>()) { 0157 connect(driveListener, SIGNAL(driveAdded()), this, SLOT(driveAdded())); 0158 connect(driveListener, SIGNAL(driveRemoved()), this, SLOT(driveRemoved())); 0159 } 0160 } 0161 # endif // Q_OS_WIN && !Q_OS_WINRT 0162 #endif 0163 start(LowPriority); 0164 } 0165 0166 /*! 0167 Destroys thread 0168 */ 0169 FileInfoGatherer::~FileInfoGatherer() 0170 { 0171 #if QT_VERSION >= 0x050e00 0172 abort.storeRelaxed(true); 0173 #else 0174 abort.store(true); 0175 #endif 0176 QMutexLocker locker(&mutex); 0177 condition.wakeAll(); 0178 locker.unlock(); 0179 wait(); 0180 } 0181 0182 void FileInfoGatherer::setResolveSymlinks(bool enable) 0183 { 0184 Q_UNUSED(enable) 0185 #ifdef Q_OS_WIN 0186 m_resolveSymlinks = enable; 0187 #endif 0188 } 0189 0190 void FileInfoGatherer::driveAdded() 0191 { 0192 fetchExtendedInformation(QString(), QStringList()); 0193 } 0194 0195 void FileInfoGatherer::driveRemoved() 0196 { 0197 QStringList drives; 0198 const QFileInfoList driveInfoList = QDir::drives(); 0199 for (const QFileInfo &fi : driveInfoList) 0200 drives.append(translateDriveName(fi)); 0201 newListOfFiles(QString(), drives); 0202 } 0203 0204 bool FileInfoGatherer::resolveSymlinks() const 0205 { 0206 #ifdef Q_OS_WIN 0207 return m_resolveSymlinks; 0208 #else 0209 return false; 0210 #endif 0211 } 0212 0213 void FileInfoGatherer::setDecorationProvider(AbstractFileDecorationProvider *provider) 0214 { 0215 m_decorationProvider = provider; 0216 } 0217 0218 AbstractFileDecorationProvider *FileInfoGatherer::decorationProvider() const 0219 { 0220 return m_decorationProvider; 0221 } 0222 0223 /*! 0224 Fetch extended information for all \a files in \a path 0225 0226 \sa updateFile(), update(), resolvedName() 0227 */ 0228 void FileInfoGatherer::fetchExtendedInformation(const QString &path, const QStringList &files) 0229 { 0230 QMutexLocker locker(&mutex); 0231 // See if we already have this dir/file in our queue 0232 int loc = this->path.lastIndexOf(path); 0233 while (loc > 0) { 0234 if (this->files.at(loc) == files) { 0235 return; 0236 } 0237 loc = this->path.lastIndexOf(path, loc - 1); 0238 } 0239 this->path.push(path); 0240 this->files.push(files); 0241 condition.wakeAll(); 0242 0243 #ifndef QT_NO_FILESYSTEMWATCHER 0244 if (files.isEmpty() 0245 && !path.isEmpty() 0246 && !path.startsWith(QLatin1String("//")) /*don't watch UNC path*/) { 0247 if (!watcher->directories().contains(path)) 0248 watcher->addPath(path); 0249 } 0250 #endif 0251 } 0252 0253 /*! 0254 Fetch extended information for all \a filePath 0255 0256 \sa fetchExtendedInformation() 0257 */ 0258 void FileInfoGatherer::updateFile(const QString &filePath) 0259 { 0260 QString dir = filePath.mid(0, filePath.lastIndexOf(QLatin1Char('/'))); 0261 QString fileName = filePath.mid(dir.length() + 1); 0262 fetchExtendedInformation(dir, QStringList(fileName)); 0263 } 0264 0265 /* 0266 List all files in \a directoryPath 0267 0268 \sa listed() 0269 */ 0270 void FileInfoGatherer::clear() 0271 { 0272 #ifndef QT_NO_FILESYSTEMWATCHER 0273 QMutexLocker locker(&mutex); 0274 watcher->removePaths(watcher->files()); 0275 watcher->removePaths(watcher->directories()); 0276 #endif 0277 0278 path.clear(); 0279 files.clear(); 0280 } 0281 0282 /* 0283 Add a \a path to the watcher 0284 */ 0285 void FileInfoGatherer::addPath(const QString &path) 0286 { 0287 #ifndef QT_NO_FILESYSTEMWATCHER 0288 QMutexLocker locker(&mutex); 0289 watcher->addPath(path); 0290 #else 0291 Q_UNUSED(path); 0292 #endif 0293 } 0294 0295 /* 0296 Remove a \a path from the watcher 0297 0298 \sa listed() 0299 */ 0300 void FileInfoGatherer::removePath(const QString &path) 0301 { 0302 #ifndef QT_NO_FILESYSTEMWATCHER 0303 QMutexLocker locker(&mutex); 0304 watcher->removePath(path); 0305 #else 0306 Q_UNUSED(path); 0307 #endif 0308 } 0309 0310 /* 0311 List all files in \a directoryPath 0312 0313 \sa listed() 0314 */ 0315 void FileInfoGatherer::list(const QString &directoryPath) 0316 { 0317 fetchExtendedInformation(directoryPath, QStringList()); 0318 } 0319 0320 /* 0321 Until aborted wait to fetch a directory or files 0322 */ 0323 void FileInfoGatherer::run() 0324 { 0325 forever { 0326 QMutexLocker locker(&mutex); 0327 #if QT_VERSION >= 0x050e00 0328 while (!abort.loadRelaxed() && path.isEmpty()) 0329 condition.wait(&mutex); 0330 if (abort.loadRelaxed()) 0331 return; 0332 #else 0333 while (!abort.load() && path.isEmpty()) 0334 condition.wait(&mutex); 0335 if (abort.load()) 0336 return; 0337 #endif 0338 #if QT_VERSION >= 0x050700 0339 const QString thisPath = qAsConst(path).front(); 0340 #else 0341 const auto constPath = path; 0342 const QString thisPath = constPath.front(); 0343 #endif 0344 path.pop_front(); 0345 #if QT_VERSION >= 0x050700 0346 const QStringList thisList = qAsConst(files).front(); 0347 #else 0348 const auto constFiles = files; 0349 const QStringList thisList = constFiles.front(); 0350 #endif 0351 files.pop_front(); 0352 locker.unlock(); 0353 0354 getFileInfos(thisPath, thisList); 0355 } 0356 } 0357 0358 ExtendedInformation FileInfoGatherer::getInfo(const QFileInfo &fileInfo) const 0359 { 0360 ExtendedInformation info(fileInfo); 0361 if (m_decorationProvider) { 0362 info.icon = m_decorationProvider->decoration(fileInfo); 0363 info.displayType = m_decorationProvider->type(fileInfo); 0364 } else { 0365 info.icon = QVariant(); 0366 info.displayType = AbstractFileDecorationProvider::fileTypeDescription(fileInfo); 0367 } 0368 #ifndef QT_NO_FILESYSTEMWATCHER 0369 // ### Not ready to listen all modifications by default 0370 static const bool watchFiles = qEnvironmentVariableIsSet("QT_FILESYSTEMMODEL_WATCH_FILES"); 0371 if (watchFiles) { 0372 if (!fileInfo.exists() && !fileInfo.isSymLink()) { 0373 watcher->removePath(fileInfo.absoluteFilePath()); 0374 } else { 0375 if (const QString filePath = fileInfo.absoluteFilePath(); 0376 !filePath.isEmpty() && fileInfo.exists() && fileInfo.isFile() && fileInfo.isReadable() 0377 && !watcher->files().contains(filePath)) { 0378 watcher->addPath(filePath); 0379 } 0380 } 0381 } 0382 #endif 0383 0384 #ifdef Q_OS_WIN 0385 if (m_resolveSymlinks && info.isSymLink(/* ignoreNtfsSymLinks = */ true)) { 0386 QFileInfo resolvedInfo(fileInfo.symLinkTarget()); 0387 resolvedInfo = QFileInfo(resolvedInfo.canonicalFilePath()); 0388 if (resolvedInfo.exists()) { 0389 emit nameResolved(fileInfo.filePath(), resolvedInfo.fileName()); 0390 } 0391 } 0392 #endif 0393 return info; 0394 } 0395 0396 /* 0397 Get specific file info's, batch the files so update when we have 100 0398 items and every 200ms after that 0399 */ 0400 void FileInfoGatherer::getFileInfos(const QString &path, const QStringList &files) 0401 { 0402 // List drives 0403 if (path.isEmpty()) { 0404 #ifdef QT_BUILD_INTERNAL 0405 fetchedRoot.store(true); 0406 #endif 0407 QFileInfoList infoList; 0408 if (files.isEmpty()) { 0409 infoList = QDir::drives(); 0410 } else { 0411 infoList.reserve(files.count()); 0412 for (const auto &file : files) 0413 infoList << QFileInfo(file); 0414 } 0415 for (int i = infoList.count() - 1; i >= 0; --i) { 0416 QString driveName = translateDriveName(infoList.at(i)); 0417 QVector<QPair<QString,QFileInfo> > updatedFiles; 0418 updatedFiles.append(QPair<QString,QFileInfo>(driveName, infoList.at(i))); 0419 emit updates(path, updatedFiles); 0420 } 0421 return; 0422 } 0423 0424 QElapsedTimer base; 0425 base.start(); 0426 QFileInfo fileInfo; 0427 bool firstTime = true; 0428 QVector<QPair<QString, QFileInfo> > updatedFiles; 0429 QStringList filesToCheck = files; 0430 0431 QStringList allFiles; 0432 if (files.isEmpty()) { 0433 QDirIterator dirIt(path, QDir::AllEntries | QDir::System | QDir::Hidden); 0434 #if QT_VERSION >= 0x050e00 0435 while (!abort.loadRelaxed() && dirIt.hasNext()) 0436 #else 0437 while (!abort.load() && dirIt.hasNext()) 0438 #endif 0439 { 0440 dirIt.next(); 0441 fileInfo = dirIt.fileInfo(); 0442 allFiles.append(fileInfo.fileName()); 0443 fetch(fileInfo, base, firstTime, updatedFiles, path); 0444 } 0445 } 0446 if (!allFiles.isEmpty()) 0447 emit newListOfFiles(path, allFiles); 0448 0449 QStringList::const_iterator filesIt = filesToCheck.constBegin(); 0450 #if QT_VERSION >= 0x050e00 0451 while (!abort.loadRelaxed() && filesIt != filesToCheck.constEnd()) 0452 #else 0453 while (!abort.load() && filesIt != filesToCheck.constEnd()) 0454 #endif 0455 { 0456 fileInfo.setFile(path + QDir::separator() + *filesIt); 0457 ++filesIt; 0458 fetch(fileInfo, base, firstTime, updatedFiles, path); 0459 } 0460 if (!updatedFiles.isEmpty()) 0461 emit updates(path, updatedFiles); 0462 emit directoryLoaded(path); 0463 } 0464 0465 void FileInfoGatherer::fetch(const QFileInfo &fileInfo, QElapsedTimer &base, bool &firstTime, QVector<QPair<QString, QFileInfo> > &updatedFiles, const QString &path) { 0466 updatedFiles.append(QPair<QString, QFileInfo>(fileInfo.fileName(), fileInfo)); 0467 QElapsedTimer current; 0468 current.start(); 0469 if ((firstTime && updatedFiles.count() > 100) || base.msecsTo(current) > 1000) { 0470 emit updates(path, updatedFiles); 0471 updatedFiles.clear(); 0472 base = current; 0473 firstTime = false; 0474 } 0475 } 0476 /** \endcond */