diff options
-rw-r--r-- | src/corelib/io/qfileinfo.cpp | 123 | ||||
-rw-r--r-- | src/corelib/io/qfileinfo.h | 17 | ||||
-rw-r--r-- | tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp | 166 |
3 files changed, 287 insertions, 19 deletions
diff --git a/src/corelib/io/qfileinfo.cpp b/src/corelib/io/qfileinfo.cpp index a5a3bc8b3e..b720966d8f 100644 --- a/src/corelib/io/qfileinfo.cpp +++ b/src/corelib/io/qfileinfo.cpp @@ -256,10 +256,9 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) \snippet code/src_corelib_io_qfileinfo.cpp 0 - On Windows, symlinks (shortcuts) are \c .lnk files. The reported - size() is that of the symlink (not the link's target), and - opening a symlink using QFile opens the \c .lnk file. For - example: + On Windows, shortcuts are \c .lnk files. The reported size() is that of + the shortcut (not the link's target), and opening a shortcut using QFile + opens the \c .lnk file. For example: \snippet code/src_corelib_io_qfileinfo.cpp 1 @@ -312,6 +311,19 @@ QDateTime &QFileInfoPrivate::getFileTime(QAbstractFileEngine::FileTime request) */ /*! + \enum QFileInfo::FileType + + This enum is returned by type() to describe the type of the file system + entity described by the QFileInfo object. + + \value Unknown The object refers to an unknown item. + \value Regular The object refers to a regular file. + \value Directory The object refers to a directory. + \value SymbolicLink The object refers to a symbolic link. + \value Shortcut The object refers to a shortcut. +*/ + +/*! \fn QFileInfo &QFileInfo::operator=(QFileInfo &&other) Move-assigns \a other to this QFileInfo instance. @@ -996,11 +1008,7 @@ bool QFileInfo::isNativePath() const */ bool QFileInfo::isFile() const { - Q_D(const QFileInfo); - return d->checkAttribute<bool>( - QFileSystemMetaData::FileType, - [d]() { return d->metaData.isFile(); }, - [d]() { return d->getFileFlags(QAbstractFileEngine::FileType); }); + return (type() & FileTypeMask) == Regular; } /*! @@ -1011,11 +1019,7 @@ bool QFileInfo::isFile() const */ bool QFileInfo::isDir() const { - Q_D(const QFileInfo); - return d->checkAttribute<bool>( - QFileSystemMetaData::DirectoryType, - [d]() { return d->metaData.isDirectory(); }, - [d]() { return d->getFileFlags(QAbstractFileEngine::DirectoryType); }); + return (type() & FileTypeMask) == Directory; } @@ -1036,7 +1040,7 @@ bool QFileInfo::isBundle() const } /*! - Returns \c true if this object points to a symbolic link; + Returns \c true if this object points to a symbolic link or shortcut; otherwise returns \c false. Symbolic links exist on Unix (including \macos and iOS) and Windows @@ -1066,6 +1070,48 @@ bool QFileInfo::isSymLink() const } /*! + \fn bool QFileInfo::isSymbolicLink() const + + Returns \c true if this object points to a symbolic link; + otherwise returns \c false. + + Symbolic links exist on Unix (including \macos and iOS) and Windows + (NTFS-symlink) and are typically created by the \c{ln -s} or \c{mklink} + commands, respectively. + + Unix handles symlinks transparently. Opening a symbolic link effectively + opens the \l{symLinkTarget()}{link's target}. + + In contrast to isSymLink(), false will be returned for shortcuts + (\c *.lnk files) on Windows. Use QFileInfo::isShortcut() instead. + + \note If the symlink points to a non existing file, exists() returns + false. + + \sa isFile(), isDir(), isShortcut(), symLinkTarget() +*/ + +/*! + \fn bool QFileInfo::isShortcut() const + + Returns \c true if this object points to a shortcut; + otherwise returns \c false. + + Shortcuts only exist on Windows and are typically \c .lnk files. + For instance, true will be returned for shortcuts (\c *.lnk files) on + Windows, but false will be returned on Unix (including \macos and iOS). + + The shortcut (.lnk) files are treated as regular files. Opening those will + open the \c .lnk file itself. In order to open the file a shortcut + references to, it must uses symLinkTarget() on a shortcut. + + \note Even if a shortcut (broken shortcut) points to a non existing file, + isShortcut() returns true. + + \sa isFile(), isDir(), isSymbolicLink(), symLinkTarget() +*/ + +/*! Returns \c true if the object points to a directory or to a symbolic link to a directory, and that directory is the root directory; otherwise returns \c false. @@ -1268,6 +1314,53 @@ qint64 QFileInfo::size() const }); } +/*! + Returns the QFileInfo::FileTypes. + + QFileInfo::FileTypes combines with an indirection flag (link type) and a + base type it refers to. + + For example, \c SymbolicLink combines with \c Regular meaning a symlink to + a regular file. + + In addition, FileTypeMask and LinkTypeMask are used to extract the base + type and link type respectively. + + \sa isFile(), isDir(), isShortcut(), isSymbolicLink() +*/ +QFileInfo::FileTypes QFileInfo::type() const +{ + Q_D(const QFileInfo); + + QFileInfo::FileTypes type = QFileInfo::Unknown; + if (d->checkAttribute<bool>( + QFileSystemMetaData::LegacyLinkType, + [d]() { return d->metaData.isLnkFile(); }, + [d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); })) { + type = QFileInfo::Shortcut; + } else if (d->checkAttribute<bool>( + QFileSystemMetaData::LegacyLinkType, + [d]() { return d->metaData.isLink(); }, + [d]() { return d->getFileFlags(QAbstractFileEngine::LinkType); })) { + type = QFileInfo::SymbolicLink; + } + + if (d->checkAttribute<bool>( + QFileSystemMetaData::DirectoryType, + [d]() { return d->metaData.isDirectory(); }, + [d]() { return d->getFileFlags(QAbstractFileEngine::DirectoryType); })) { + return type | QFileInfo::Directory; + } + + if (d->checkAttribute<bool>( + QFileSystemMetaData::FileType, + [d]() { return d->metaData.isFile(); }, + [d]() { return d->getFileFlags(QAbstractFileEngine::FileType); })) { + return type | QFileInfo::Regular; + } + return type; +} + #if QT_DEPRECATED_SINCE(5, 10) /*! \deprecated diff --git a/src/corelib/io/qfileinfo.h b/src/corelib/io/qfileinfo.h index 111517325d..1cbeafdd4a 100644 --- a/src/corelib/io/qfileinfo.h +++ b/src/corelib/io/qfileinfo.h @@ -66,6 +66,20 @@ public: QFileInfo(const QFileInfo &fileinfo); ~QFileInfo(); + enum FileType { + Unknown, + // base type + Regular, + Directory, + // indirection flag + SymbolicLink = 0x10, + Shortcut = 0x20, + // mask + FileTypeMask = 0x0f, + LinkTypeMask = 0xf0 + }; + Q_DECLARE_FLAGS(FileTypes, FileType) + QFileInfo &operator=(const QFileInfo &fileinfo); QFileInfo &operator=(QFileInfo &&other) noexcept { swap(other); return *this; } @@ -111,6 +125,8 @@ public: bool isFile() const; bool isDir() const; bool isSymLink() const; + inline bool isSymbolicLink() const { return type() & SymbolicLink; } + inline bool isShortcut() const { return type() & Shortcut; } bool isRoot() const; bool isBundle() const; @@ -129,6 +145,7 @@ public: QFile::Permissions permissions() const; qint64 size() const; + FileTypes type() const; // ### Qt6: inline these functions #if QT_DEPRECATED_SINCE(5, 10) diff --git a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp index 60e320c44d..6fcfe87c83 100644 --- a/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp +++ b/tests/auto/corelib/io/qfileinfo/tst_qfileinfo.cpp @@ -236,6 +236,8 @@ private slots: void isSymLink_data(); void isSymLink(); + void link_data(); + void link(); void isHidden_data(); void isHidden(); @@ -277,6 +279,9 @@ private slots: void invalidState(); void nonExistingFile(); + void type_data(); + void type(); + private: const QString m_currentDir; QString m_sourceFile; @@ -1337,6 +1342,88 @@ void tst_QFileInfo::isSymLink() #endif } +Q_DECLARE_METATYPE(QFileInfo::FileType) + +void tst_QFileInfo::link_data() +{ + QFile::remove("link"); + QFile::remove("link.lnk"); + QFile::remove("brokenlink"); + QFile::remove("brokenlink.lnk"); + QFile::remove("dummyfile"); + QFile::remove("relative/link"); + + QTest::addColumn<QString>("path"); + QTest::addColumn<QFileInfo::FileType>("linkType"); + QTest::addColumn<QString>("linkTarget"); + + QFile file1(m_sourceFile); + QFile file2("dummyfile"); + file2.open(QIODevice::WriteOnly); + + QTest::newRow("existent file") << m_sourceFile << QFileInfo::Unknown << ""; +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) + // windows shortcuts + QVERIFY(file1.link("link.lnk")); + QTest::newRow("link.lnk") + << "link.lnk" << QFileInfo::Shortcut << QFileInfo(m_sourceFile).absoluteFilePath(); + + QVERIFY(file2.link("brokenlink.lnk")); + QTest::newRow("broken link.lnk") + << "brokenlink.lnk" << QFileInfo::Shortcut << QFileInfo("dummyfile").absoluteFilePath(); +#endif + +#ifndef Q_NO_SYMLINKS +#if defined(Q_OS_WIN) +#if !defined(Q_OS_WINRT) + QString errorMessage; + DWORD creationResult = createSymbolicLink("link", m_sourceFile, &errorMessage); + if (creationResult == ERROR_PRIVILEGE_NOT_HELD) { + QWARN(msgInsufficientPrivileges(errorMessage)); + } else { + QVERIFY2(creationResult == ERROR_SUCCESS, qPrintable(errorMessage)); + QTest::newRow("link") + << "link" << QFileInfo::SymbolicLink << QFileInfo(m_sourceFile).absoluteFilePath(); + } + + creationResult = createSymbolicLink("brokenlink", "dummyfile", &errorMessage); + if (creationResult == ERROR_PRIVILEGE_NOT_HELD) { + QWARN(msgInsufficientPrivileges(errorMessage)); + } else { + QVERIFY2(creationResult == ERROR_SUCCESS, qPrintable(errorMessage)); + QTest::newRow("broken link") + << "brokenlink" << QFileInfo::SymbolicLink << QFileInfo("dummyfile").absoluteFilePath(); + } +#endif // !Q_OS_WINRT +#else // Unix: + QVERIFY(file1.link("link")); + QTest::newRow("link") + << "link" << QFileInfo::SymbolicLink << QFileInfo(m_sourceFile).absoluteFilePath(); + + QVERIFY(file2.link("brokenlink")); + QTest::newRow("broken link") + << "brokenlink" << QFileInfo::SymbolicLink << QFileInfo("dummyfile").absoluteFilePath(); + + QDir::current().mkdir("relative"); + QFile::link("../dummyfile", "relative/link"); + QTest::newRow("relative link") + << "relative/link" << QFileInfo::SymbolicLink << QFileInfo("dummyfile").absoluteFilePath(); +#endif +#endif // !Q_NO_SYMLINKS + file2.remove(); +} + +void tst_QFileInfo::link() +{ + QFETCH(QString, path); + QFETCH(QFileInfo::FileType, linkType); + QFETCH(QString, linkTarget); + + QFileInfo fi(path); + QCOMPARE(fi.type() & QFileInfo::LinkTypeMask, linkType); + QCOMPARE(fi.symLinkTarget(), linkTarget); +} + void tst_QFileInfo::isHidden_data() { QTest::addColumn<QString>("path"); @@ -1638,7 +1725,7 @@ void tst_QFileInfo::ntfsJunctionPointsAndSymlinks() QVERIFY2(creationResult == ERROR_SUCCESS, qPrintable(errorMessage)); QFileInfo fi(path); - const bool actualIsSymLink = fi.isSymLink(); + const bool actualIsSymLink = fi.isSymbolicLink(); const QString actualSymLinkTarget = isSymLink ? fi.symLinkTarget() : QString(); const QString actualCanonicalFilePath = isSymLink ? fi.canonicalFilePath() : QString(); // Ensure that junctions, mountpoints are removed. If this fails, do not remove @@ -1667,14 +1754,16 @@ void tst_QFileInfo::brokenShortcut() file.close(); QFileInfo info(linkName); - QVERIFY(info.isSymLink()); + QVERIFY(!info.isSymbolicLink()); + QVERIFY(info.isShortcut()); QVERIFY(!info.exists()); QFile::remove(linkName); QDir current; // QTBUG-21863 QVERIFY(current.mkdir(linkName)); QFileInfo dirInfo(linkName); - QVERIFY(!dirInfo.isSymLink()); + QVERIFY(!dirInfo.isSymbolicLink()); + QVERIFY(!dirInfo.isShortcut()); QVERIFY(dirInfo.isDir()); current.rmdir(linkName); } @@ -2032,7 +2121,8 @@ static void stateCheck(const QFileInfo &info, const QString &dirname, const QStr QVERIFY(!info.isHidden()); QVERIFY(!info.isFile()); QVERIFY(!info.isDir()); - QVERIFY(!info.isSymLink()); + QVERIFY(!info.isSymbolicLink()); + QVERIFY(!info.isShortcut()); QVERIFY(!info.isBundle()); QVERIFY(!info.isRoot()); QCOMPARE(info.isNativePath(), !filename.isEmpty()); @@ -2089,5 +2179,73 @@ void tst_QFileInfo::nonExistingFile() stateCheck(info, dirname, filename); } +Q_DECLARE_METATYPE(QFileInfo::FileTypes) + +void tst_QFileInfo::type_data() +{ + QFile::remove("link.lnk"); + QFile::remove("symlink.lnk"); + QFile::remove("link"); + QFile::remove("symlink"); + QFile::remove("directory.lnk"); + QFile::remove("directory"); + + QTest::addColumn<QString>("path"); + QTest::addColumn<QFileInfo::FileTypes>("type"); + + QFile regularFile(m_sourceFile); + QTest::newRow("regular") + << regularFile.fileName() << QFileInfo::FileTypes(QFileInfo::Regular); + QTest::newRow("directory") + << QDir::currentPath() << QFileInfo::FileTypes(QFileInfo::Directory); +#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT) + // windows shortcuts + QVERIFY(regularFile.link("link.lnk")); + QTest::newRow("shortcut") + << "link.lnk" << QFileInfo::FileTypes(QFileInfo::Shortcut | QFileInfo::Regular); + QVERIFY(regularFile.link("link")); + QTest::newRow("invalid-shortcut") + << "link" << QFileInfo::FileTypes(QFileInfo::Regular); + QVERIFY(QFile::link(QDir::currentPath(), "directory.lnk")); + QTest::newRow("directory-shortcut") + << "directory.lnk" << QFileInfo::FileTypes(QFileInfo::Shortcut | QFileInfo::Directory); +#endif + +#ifndef Q_NO_SYMLINKS +#if defined(Q_OS_WIN) +#if !defined(Q_OS_WINRT) + QString errorMessage; + const DWORD creationResult = createSymbolicLink("symlink", m_sourceFile, &errorMessage); + if (creationResult == ERROR_PRIVILEGE_NOT_HELD) { + QWARN(msgInsufficientPrivileges(errorMessage)); + } else { + QVERIFY2(creationResult == ERROR_SUCCESS, qPrintable(errorMessage)); + QTest::newRow("NTFS-symlink") + << "symlink" << QFileInfo::FileTypes(QFileInfo::SymbolicLink | QFileInfo::Regular); + } +#endif // !Q_OS_WINRT +#else // Unix: + QVERIFY(regularFile.link("symlink.lnk")); + QTest::newRow("symlink.lnk") + << "symlink.lnk" << QFileInfo::FileTypes(QFileInfo::SymbolicLink | QFileInfo::Regular); + QVERIFY(regularFile.link("symlink")); + QTest::newRow("symlink") + << "symlink" << QFileInfo::FileTypes(QFileInfo::SymbolicLink | QFileInfo::Regular); + QVERIFY(QFile::link(QDir::currentPath(), "directory")); + QTest::newRow("directory-symlink") + << "directory" << QFileInfo::FileTypes(QFileInfo::SymbolicLink | QFileInfo::Directory); +#endif +#endif // !Q_NO_SYMLINKS +} + +void tst_QFileInfo::type() +{ + QFETCH(QString, path); + QFETCH(QFileInfo::FileTypes, type); + + QFileInfo info(path); + QCOMPARE(info.type(), type); +} + QTEST_MAIN(tst_QFileInfo) #include "tst_qfileinfo.moc" |