diff --git a/autotests/jobremotetest.cpp b/autotests/jobremotetest.cpp --- a/autotests/jobremotetest.cpp +++ b/autotests/jobremotetest.cpp @@ -67,7 +67,7 @@ static bool myExists(const QUrl &url) { - KIO::Job *job = KIO::stat(url, KIO::StatJob::DestinationSide, 0, KIO::HideProgressInfo); + KIO::Job *job = KIO::statDetails(url, KIO::StatJob::DestinationSide, KIO::StatDetail::Basic, KIO::HideProgressInfo); job->setUiDelegate(nullptr); return job->exec(); } diff --git a/autotests/jobtest.h b/autotests/jobtest.h --- a/autotests/jobtest.h +++ b/autotests/jobtest.h @@ -83,6 +83,8 @@ void rmdirEmpty(); void rmdirNotEmpty(); void stat(); + void statDetailsBasic(); + void statDetailsBasicSetDetails(); #ifndef Q_OS_WIN void statSymlink(); #endif diff --git a/autotests/jobtest.cpp b/autotests/jobtest.cpp --- a/autotests/jobtest.cpp +++ b/autotests/jobtest.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "kiotesthelper.h" // createTestFile etc. #ifndef Q_OS_WIN #include // for readlink @@ -114,7 +115,7 @@ void JobTest::storedGet() { - qDebug(); + // qDebug(); const QString filePath = homeTmpDir() + "fileFromHome"; createTestFile(filePath); QUrl u = QUrl::fromLocalFile(filePath); @@ -599,16 +600,16 @@ void JobTest::copyDirectoryToSamePartition() { - qDebug(); + // qDebug(); const QString src = homeTmpDir() + "dirFromHome"; const QString dest = homeTmpDir() + "dirFromHome_copied"; createTestDirectory(src); copyLocalDirectory(src, dest); } void JobTest::copyDirectoryToExistingDirectory() { - qDebug(); + // qDebug(); // just the same as copyDirectoryToSamePartition, but this time dest exists. // So we get a subdir, "dirFromHome_copy/dirFromHome" const QString src = homeTmpDir() + "dirFromHome"; @@ -620,16 +621,16 @@ void JobTest::copyFileToOtherPartition() { - qDebug(); + // qDebug(); const QString filePath = homeTmpDir() + "fileFromHome"; const QString dest = otherTmpDir() + "fileFromHome_copied"; createTestFile(filePath); copyLocalFile(filePath, dest); } void JobTest::copyDirectoryToOtherPartition() { - qDebug(); + // qDebug(); const QString src = homeTmpDir() + "dirFromHome"; const QString dest = otherTmpDir() + "dirFromHome_copied"; createTestDirectory(src); @@ -676,6 +677,7 @@ createTestDirectory(src_dir); createTestDirectory(src_dir + "/folder1"); QString inaccessible = src_dir + "/folder1/inaccessible"; + createTestDirectory(inaccessible); QFile(inaccessible).setPermissions(QFile::Permissions()); // Make it inaccessible @@ -1381,8 +1383,21 @@ KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); QVERIFY(job); QVERIFY2(job->exec(), qPrintable(job->errorString())); - // TODO set setSide, setDetails + // TODO set setSide const KIO::UDSEntry &entry = job->statResult(); + + // we only get filename, access, type, size, uid, gid, btime, mtime, atime + QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_USER)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_GROUP)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME)); + QCOMPARE(entry.count(), 9); + QVERIFY(!entry.isDir()); QVERIFY(!entry.isLink()); QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); @@ -1413,6 +1428,83 @@ #endif } +void JobTest::statDetailsBasic() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + const QUrl url(QUrl::fromLocalFile(filePath)); + KIO::StatJob *job = KIO::statDetails(url, KIO::StatJob::StatSide::SourceSide, KIO::StatDetail::Basic, KIO::HideProgressInfo); + QVERIFY(job); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + // TODO set setSide + const KIO::UDSEntry &entry = job->statResult(); + + // we only get filename, access, type, size, (no linkdest) + QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); + QCOMPARE(entry.count(), 4); + + QVERIFY(!entry.isDir()); + QVERIFY(!entry.isLink()); + QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0); + QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); + + // Compare what we get via kio_file and what we get when KFileItem stat()s directly + // for the requested fields + const KFileItem kioItem(entry, url); + const KFileItem fileItem(url); + QCOMPARE(kioItem.name(), fileItem.name()); + QCOMPARE(kioItem.url(), fileItem.url()); + QCOMPARE(kioItem.size(), fileItem.size()); + QCOMPARE(kioItem.user(), ""); + QCOMPARE(kioItem.group(), ""); + QCOMPARE(kioItem.mimetype(), "application/octet-stream"); + QCOMPARE(kioItem.permissions(), 438); + QCOMPARE(kioItem.time(KFileItem::ModificationTime), QDateTime()); + QCOMPARE(kioItem.time(KFileItem::AccessTime), QDateTime()); +} + +void JobTest::statDetailsBasicSetDetails() +{ + const QString filePath = homeTmpDir() + "fileFromHome"; + createTestFile(filePath); + const QUrl url(QUrl::fromLocalFile(filePath)); + KIO::StatJob *job = KIO::stat(url); + job->setDetails(KIO::StatDetail::Basic); + QVERIFY(job); + QVERIFY2(job->exec(), qPrintable(job->errorString())); + // TODO set setSide + const KIO::UDSEntry &entry = job->statResult(); + + // we only get filename, access, type, size, (no linkdest) + QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); + QCOMPARE(entry.count(), 4); + + QVERIFY(!entry.isDir()); + QVERIFY(!entry.isLink()); + QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0); + QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); + + // Compare what we get via kio_file and what we get when KFileItem stat()s directly + // for the requested fields + const KFileItem kioItem(entry, url); + const KFileItem fileItem(url); + QCOMPARE(kioItem.name(), fileItem.name()); + QCOMPARE(kioItem.url(), fileItem.url()); + QCOMPARE(kioItem.size(), fileItem.size()); + QCOMPARE(kioItem.user(), ""); + QCOMPARE(kioItem.group(), ""); + QCOMPARE(kioItem.mimetype(), "application/octet-stream"); + QCOMPARE(kioItem.permissions(), 438); + QCOMPARE(kioItem.time(KFileItem::ModificationTime), QDateTime()); + QCOMPARE(kioItem.time(KFileItem::AccessTime), QDateTime()); +} + #ifndef Q_OS_WIN void JobTest::statSymlink() { @@ -1424,13 +1516,30 @@ setTimeStamp(symlink, QDateTime::currentDateTime().addSecs(-20)); // differentiate link time and source file time const QUrl url(QUrl::fromLocalFile(symlink)); - KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); + KIO::StatJob *job = KIO::statDetails(url, KIO::StatJob::StatSide::SourceSide, + KIO::StatDetail::Basic | KIO::StatDetail::ResolveSymlink | KIO::StatDetail::User | KIO::StatDetail::Time, + KIO::HideProgressInfo); QVERIFY(job); QVERIFY2(job->exec(), qPrintable(job->errorString())); // TODO set setSide, setDetails const KIO::UDSEntry &entry = job->statResult(); + + // we only get filename, access, type, size, linkdest, uid, gid, btime, mtime, atime + QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_LINK_DEST)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_USER)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_GROUP)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)); + QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME)); + QCOMPARE(entry.count(), 10); + QVERIFY(!entry.isDir()); QVERIFY(entry.isLink()); + QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0); QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("link")); // Compare what we get via kio_file and what we get when KFileItem stat()s directly diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -167,7 +167,7 @@ GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 - DEPRECATION_VERSIONS 3.0 3.1 3.4 4.0 4.3 4.5 4.6 5.0 5.2 5.8 5.24 5.45 5.48 5.63 5.61 5.64 5.65 5.66 + DEPRECATION_VERSIONS 3.0 3.1 3.4 4.0 4.3 4.5 4.6 5.0 5.2 5.8 5.24 5.45 5.48 5.63 5.61 5.64 5.65 5.66 5.69 ) # TODO: add support for EXCLUDE_DEPRECATED_BEFORE_AND_AT to all KIO libs # needs fixing of undeprecated API being still implemented using own deprecated API diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp --- a/src/core/copyjob.cpp +++ b/src/core/copyjob.cpp @@ -19,6 +19,7 @@ Boston, MA 02110-1301, USA. */ +#include "global.h" #include "copyjob.h" #include "kiocoredebug.h" #include @@ -362,7 +363,7 @@ // Stat the dest state = STATE_STATING; const QUrl dest = m_asMethod ? m_dest.adjusted(QUrl::RemoveFilename) : m_dest; - KIO::Job *job = KIO::stat(dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); + KIO::Job *job = KIO::statDetails(dest, StatJob::DestinationSide, KIO::StatDetail::StatDefaultDetails, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "CopyJob: stating the dest" << m_dest; q->addSubjob(job); } @@ -907,7 +908,7 @@ } // Stat the next src url - Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); + Job *job = KIO::statDetails(m_currentSrcURL, StatJob::SourceSide, KIO::StatDetail::StatDefaultDetails, KIO::HideProgressInfo); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; state = STATE_STATING; q->addSubjob(job); @@ -1138,7 +1139,7 @@ // We need to stat the existing dir, to get its last-modification time QUrl existingDest((*it).uDest); - SimpleJob *newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); + SimpleJob *newJob = KIO::statDetails(existingDest, StatJob::DestinationSide, KIO::StatDetail::StatDefaultDetails, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingDest; state = STATE_CONFLICT_CREATING_DIRS; @@ -1351,7 +1352,7 @@ Q_ASSERT(!q->hasSubjobs()); // We need to stat the existing file, to get its last-modification time QUrl existingFile((*it).uDest); - SimpleJob *newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo); + SimpleJob *newJob = KIO::statDetails(existingFile, StatJob::DestinationSide, KIO::StatDetail::StatDefaultDetails, KIO::HideProgressInfo); Scheduler::setJobPriority(newJob, 1); qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat for resolving conflict on" << existingFile; state = STATE_CONFLICT_COPYING_FILES; @@ -2004,7 +2005,7 @@ m_dest = destDirectory; m_dest.setPath(concatPaths(m_dest.path(), newName)); emit q->renamed(q, dest, m_dest); - KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); + KIO::Job *job = KIO::statDetails(m_dest, StatJob::DestinationSide, KIO::StatDefaultDetails, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); @@ -2096,7 +2097,7 @@ // This is only for this src url; the next one will revert to m_globalDest m_dest.setPath(newPath); emit q->renamed(q, dest, m_dest); // for e.g. KPropertiesDialog - KIO::Job *job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo); + KIO::Job *job = KIO::statDetails(m_dest, StatJob::DestinationSide, KIO::StatDefaultDetails, KIO::HideProgressInfo); state = STATE_STATING; destinationState = DEST_NOT_STATED; q->addSubjob(job); @@ -2150,7 +2151,7 @@ } qCDebug(KIO_COPYJOB_DEBUG) << "Couldn't rename" << m_currentSrcURL << "to" << dest << ", reverting to normal way, starting with stat"; qCDebug(KIO_COPYJOB_DEBUG) << "KIO::stat on" << m_currentSrcURL; - KIO::Job *job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo); + KIO::Job *job = KIO::statDetails(m_currentSrcURL, StatJob::SourceSide, KIO::StatDefaultDetails, KIO::HideProgressInfo); state = STATE_STATING; q->addSubjob(job); m_bOnlyRenames = false; diff --git a/src/core/deletejob.cpp b/src/core/deletejob.cpp --- a/src/core/deletejob.cpp +++ b/src/core/deletejob.cpp @@ -21,6 +21,7 @@ #include "deletejob.h" +#include "global.h" #include "job.h" // buildErrorString #include "statjob.h" #include "listjob.h" @@ -314,7 +315,7 @@ // Done, jump to the last else of this method statNextSrc(); } else { - KIO::SimpleJob *job = KIO::stat(m_currentURL, StatJob::SourceSide, 0, KIO::HideProgressInfo); + KIO::SimpleJob *job = KIO::statDetails(m_currentURL, StatJob::SourceSide, KIO::StatDetail::Basic, KIO::HideProgressInfo); Scheduler::setJobPriority(job, 1); //qDebug() << "stat'ing" << m_currentURL; q->addSubjob(job); @@ -501,7 +502,11 @@ if (!KProtocolManager::canDeleteRecursive(url)) { //qDebug() << url << "is a directory, let's list it"; ListJob *newjob = KIO::listRecursive(url, KIO::HideProgressInfo); +#if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69) + // TODO KF6: remove legacy details code path newjob->addMetaData(QStringLiteral("details"), QStringLiteral("0")); +#endif + newjob->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::Basic)); newjob->setUnrestricted(true); // No KIOSK restrictions Scheduler::setJobPriority(newjob, 1); QObject::connect(newjob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), diff --git a/src/core/directorysizejob.cpp b/src/core/directorysizejob.cpp --- a/src/core/directorysizejob.cpp +++ b/src/core/directorysizejob.cpp @@ -17,6 +17,7 @@ Boston, MA 02110-1301, USA. */ +#include "global.h" #include "directorysizejob.h" #include "listjob.h" #include @@ -110,6 +111,7 @@ Q_Q(DirectorySizeJob); while (m_currentItem < m_lstItems.count()) { const KFileItem item = m_lstItems[m_currentItem++]; + // qDebug() << item; if (!item.isLink()) { if (item.isDir()) { //qDebug() << "dir -> listing"; @@ -133,7 +135,12 @@ Q_Q(DirectorySizeJob); //qDebug() << url; KIO::ListJob *listJob = KIO::listRecursive(url, KIO::HideProgressInfo); +#if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69) + // TODO KF6: remove legacy details code path listJob->addMetaData(QStringLiteral("details"), QStringLiteral("3")); +#endif + listJob->addMetaData(QStringLiteral("statDetails"), + QString::number(KIO::StatDetail::Basic | KIO::StatDetail::ResolveSymlink | KIO::StatDetail::Inode)); q->connect(listJob, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(slotEntries(KIO::Job*,KIO::UDSEntryList))); q->addSubjob(listJob); @@ -146,6 +153,7 @@ for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; + const long device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID, 0); if (device) { // Hard-link detection (#67939) diff --git a/src/core/global.h b/src/core/global.h --- a/src/core/global.h +++ b/src/core/global.h @@ -312,6 +312,34 @@ OperationNotAllowed }; +/** + * Describes the fields that a stat command will retrieve + * @see UDSEntry + * @since 5.69 + */ +enum StatDetail { + /// No field returned, useful to check if a file exists + NoDetails = 0x0, + /// Filename, access, type, size, linkdest + Basic = 0x1, + /// uid, gid + User = 0x2, + /// atime, mtime, btime + Time = 0x4, + /// Resolve symlinks + ResolveSymlink = 0x8, + /// acl Data + Acl = 0x10, + /// dev, inode + Inode = 0x20, + + /// Default value includes fields provided by other entries + StatDefaultDetails = Basic | User | Time | Acl | ResolveSymlink, +}; +Q_DECLARE_FLAGS(StatDetails, StatDetail) + +Q_DECLARE_OPERATORS_FOR_FLAGS(KIO::StatDetails) + /** * Parses the string representation of the cache control option. * diff --git a/src/core/statjob.h b/src/core/statjob.h --- a/src/core/statjob.h +++ b/src/core/statjob.h @@ -21,6 +21,7 @@ #ifndef KIO_STATJOB_H #define KIO_STATJOB_H +#include "global.h" #include "simplejob.h" #include @@ -70,16 +71,32 @@ void setSide(bool source); #endif + /** + * Selects the level of @p details we want. + * @since 5.69 + */ + void setDetails(KIO::StatDetails details); + +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 69) + /** + * @brief @see setDetails(KIO::StatDetails details) + * Needed until setDetails(short int details) is removed + */ + void setDetails(KIO::StatDetail detail); + /** * Selects the level of @p details we want. * By default this is 2 (all details wanted, including modification time, size, etc.), * setDetails(1) is used when deleting: we don't need all the information if it takes * too much time, no need to follow symlinks etc. * setDetails(0) is used for very simple probing: we'll only get the answer * "it's a file or a directory, or it doesn't exist". This is used by KRun. * @param details 2 for all details, 1 for simple, 0 for very simple + * @deprecated since 5.69, use setDetails(KIO::StatDetails) */ + KIOCORE_DEPRECATED_VERSION(5, 69, "Use setDetails(KIO::statDetails)") void setDetails(short int details); +#endif /** * @brief Result of the stat operation. @@ -156,6 +173,34 @@ * @return the job handling the operation. */ KIOCORE_EXPORT StatJob *stat(const QUrl &url, JobFlags flags = DefaultFlags); +/** + * Find all details for one file or directory. + * This version of the call includes two additional booleans, @p sideIsSource and @p details. + * + * @param url the URL of the file + * @param side is SourceSide when stating a source file (we will do a get on it if + * the stat works) and DestinationSide when stating a destination file (target of a copy). + * The reason for this parameter is that in some cases the kioslave might not + * be able to determine a file's existence (e.g. HTTP doesn't allow it, FTP + * has issues with case-sensitivity on some systems). + * When the slave can't reliably determine the existence of a file, it will: + * @li be optimistic if SourceSide, i.e. it will assume the file exists, + * and if it doesn't this will appear when actually trying to download it + * @li be pessimistic if DestinationSide, i.e. it will assume the file + * doesn't exist, to prevent showing "about to overwrite" errors to the user. + * If you simply want to check for existence without downloading/uploading afterwards, + * then you should use DestinationSide. + * + * @param details selects the level of details we want. + * You should minimize the detail level for better performance. + * @param flags Can be HideProgressInfo here + * @return the job handling the operation. + * @since 5.69 + */ +KIOCORE_EXPORT StatJob *statDetails(const QUrl &url, KIO::StatJob::StatSide side, + KIO::StatDetails details = KIO::StatDetail::StatDefaultDetails, JobFlags flags = DefaultFlags); + +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 68) /** * Find all details for one file or directory. * This version of the call includes two additional booleans, @p sideIsSource and @p details. @@ -182,10 +227,23 @@ * "it's a file or a directory or a symlink, or it doesn't exist". This is used by KRun and DeleteJob. * @param flags Can be HideProgressInfo here * @return the job handling the operation. + * @deprecated since 5.69, use statDetails(const QUrl &, KIO::StatSide, KIO::StatDetails, JobFlags) */ +KIOCORE_DEPRECATED_VERSION(5, 69, "Use KIO::statDetails(const QUrl &, KIO::StatSide, KIO::StatDetails details, JobFlags)") KIOCORE_EXPORT StatJob *stat(const QUrl &url, KIO::StatJob::StatSide side, short int details, JobFlags flags = DefaultFlags); +/** + * Converts the legacy stat details int to a StatDetail Flag + * @param details @see setDetails() + * @since 5.69 + * @deprecated since 5.69, use directly KIO::StatDetails + */ +KIOCORE_DEPRECATED_VERSION(5, 69, "Use directly KIO::StatDetails") +KIOCORE_EXPORT KIO::StatDetails detailsToStatDetails(int details); + +#endif + #if KIOCORE_ENABLE_DEPRECATED_SINCE(4, 0) /** * Find all details for one file or directory. diff --git a/src/core/statjob.cpp b/src/core/statjob.cpp --- a/src/core/statjob.cpp +++ b/src/core/statjob.cpp @@ -32,13 +32,13 @@ { public: inline StatJobPrivate(const QUrl &url, int command, const QByteArray &packedArgs) - : SimpleJobPrivate(url, command, packedArgs), m_bSource(true), m_details(2) + : SimpleJobPrivate(url, command, packedArgs), m_bSource(true), m_details(KIO::StatDefaultDetails) {} UDSEntry m_statResult; QUrl m_redirectionURL; bool m_bSource; - short int m_details; + KIO::StatDetails m_details; void slotStatEntry(const KIO::UDSEntry &entry); void slotRedirection(const QUrl &url); @@ -84,11 +84,39 @@ d_func()->m_bSource = side == SourceSide; } -void StatJob::setDetails(short int details) +void StatJob::setDetails(KIO::StatDetails details) { d_func()->m_details = details; } +#if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69) +void StatJob::setDetails(short int details) +{ + // for backward compatibility + d_func()->m_details = detailsToStatDetails(details); +} + +void StatJob::setDetails(KIO::StatDetail detail) +{ + d_func()->m_details = detail; +} + +KIO::StatDetails KIO::detailsToStatDetails(int details) +{ + KIO::StatDetails detailsFlag = KIO::StatDetail::Basic; + if (details > 0) { + detailsFlag |= KIO::StatDetail::User | KIO::StatDetail::Time; + } + if (details > 1) { + detailsFlag |= KIO::StatDetail::ResolveSymlink | KIO::StatDetail::Acl; + } + if (details > 2) { + detailsFlag |= KIO::StatDetail::Inode; + } + return detailsFlag; +} +#endif + const UDSEntry &StatJob::statResult() const { return d_func()->m_statResult; @@ -110,7 +138,7 @@ { Q_Q(StatJob); m_outgoingMetaData.insert(QStringLiteral("statSide"), m_bSource ? QStringLiteral("source") : QStringLiteral("dest")); - m_outgoingMetaData.insert(QStringLiteral("details"), QString::number(m_details)); + m_outgoingMetaData.insert(QStringLiteral("statDetails"), QString::number(m_details)); q->connect(slave, SIGNAL(statEntry(KIO::UDSEntry)), SLOT(slotStatEntry(KIO::UDSEntry))); @@ -176,12 +204,12 @@ StatJob *KIO::stat(const QUrl &url, JobFlags flags) { // Assume sideIsSource. Gets are more common than puts. - return stat(url, StatJob::SourceSide, 2, flags); + return statDetails(url, StatJob::SourceSide, KIO::StatDefaultDetails, flags); } StatJob *KIO::mostLocalUrl(const QUrl &url, JobFlags flags) { - StatJob *job = stat(url, StatJob::SourceSide, 2, flags); + StatJob *job = statDetails(url, StatJob::SourceSide, KIO::StatDefaultDetails, flags); if (url.isLocalFile()) { QTimer::singleShot(0, job, &StatJob::slotFinished); Scheduler::cancelJob(job); // deletes the slave if not 0 @@ -199,6 +227,17 @@ return job; } +StatJob *KIO::statDetails(const QUrl &url, KIO::StatJob::StatSide side, KIO::StatDetails details, JobFlags flags) +{ + // TODO KF6: rename to stat + //qCDebug(KIO_CORE) << "stat" << url; + KIO_ARGS << url; + StatJob *job = StatJobPrivate::newJob(url, CMD_STAT, packedArgs, flags); + job->setSide(side); + job->setDetails(details); + return job; +} + StatJob *KIO::stat(const QUrl &url, KIO::StatJob::StatSide side, short int details, JobFlags flags) { //qCDebug(KIO_CORE) << "stat" << url; diff --git a/src/filewidgets/kdiroperator.cpp b/src/filewidgets/kdiroperator.cpp --- a/src/filewidgets/kdiroperator.cpp +++ b/src/filewidgets/kdiroperator.cpp @@ -742,7 +742,7 @@ } else { KIO::StatJob *job = KIO::stat(folderurl); KJobWidgets::setWindow(job, this); - job->setDetails(0); //We only want to know if it exists, 0 == that. + job->setDetails(KIO::StatDetail::NoDetails); //We only want to know if it exists job->setSide(KIO::StatJob::DestinationSide); exists = job->exec(); } diff --git a/src/ioslaves/file/file.h b/src/ioslaves/file/file.h --- a/src/ioslaves/file/file.h +++ b/src/ioslaves/file/file.h @@ -95,7 +95,7 @@ private: bool createUDSEntry(const QString &filename, const QByteArray &path, KIO::UDSEntry &entry, - short int details); + KIO::StatDetails details); int setACL(const char *path, mode_t perm, bool _directoryDefault); QString getUserName(KUserId uid) const; QString getGroupName(KGroupId gid) const; @@ -123,6 +123,7 @@ QFile *mFile; bool testMode = false; + KIO::StatDetails getStatDetails(); }; #endif diff --git a/src/ioslaves/file/file.cpp b/src/ioslaves/file/file.cpp --- a/src/ioslaves/file/file.cpp +++ b/src/ioslaves/file/file.cpp @@ -865,11 +865,42 @@ #if HAVE_STATX // statx syscall is available -inline int LSTAT(const char* path, struct statx * buff) { - return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, buff); +inline int LSTAT(const char* path, struct statx * buff, KIO::StatDetails details) { + uint32_t mask = 0; + if (details & KIO::Basic) { + // filename, access, type, size, linkdest + mask |= STATX_SIZE | STATX_TYPE; + } + if (details & KIO::User) { + // uid, gid + mask |= STATX_UID | STATX_GID; + } + if (details & KIO::Time) { + // atime, mtime, btime + mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME; + } + if (details & KIO::Inode) { + // dev, inode + mask |= STATX_INO; + } + return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask, buff); } -inline int STAT(const char* path, struct statx * buff) { - return statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, STATX_BASIC_STATS | STATX_BTIME, buff); +inline int STAT(const char* path, struct statx * buff, KIO::StatDetails details) { + uint32_t mask = 0; + if (details & KIO::Basic) { + // filename, access, type, size, linkdest + mask |= STATX_SIZE | STATX_TYPE; + } + if (details & KIO::User) { + // uid, gid + mask |= STATX_UID | STATX_GID; + } + if (details & KIO::Time) { + // atime, mtime, btime + mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME; + } + // KIO::Inode is ignored as when STAT is called, the entry inode field has already been filled + return statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, mask, buff); } inline static uint16_t stat_mode(struct statx &buf) { return buf.stx_mode; } inline static uint32_t stat_dev(struct statx &buf) { return buf.stx_dev_major; } @@ -881,10 +912,12 @@ inline static int64_t stat_mtime(struct statx &buf) { return buf.stx_mtime.tv_sec; } #else // regular stat struct -inline int LSTAT(const char* path, QT_STATBUF * buff) { +inline int LSTAT(const char* path, QT_STATBUF * buff, KIO::StatDetails details) { + Q_UNUSED(details) return QT_LSTAT(path, buff); } -inline int STAT(const char* path, QT_STATBUF * buff) { +inline int STAT(const char* path, QT_STATBUF * buff, KIO::StatDetails details) { + Q_UNUSED(details) return QT_STAT(path, buff); } inline static mode_t stat_mode(QT_STATBUF &buf) { return buf.st_mode; } @@ -900,28 +933,35 @@ #endif bool FileProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, - short int details) + KIO::StatDetails details) { assert(entry.count() == 0); // by contract :-) - switch (details) { - case 0: + int entries = 0; + if (details & KIO::Basic) { // filename, access, type, size, linkdest - entry.reserve(5); - break; - case 1: - // uid, gid, atime, mtime, btime - entry.reserve(10); - break; - case 2: + entries += 5; + } + if (details & KIO::User) { + // uid, gid + entries += 2; + } + if (details & KIO::Time) { + // atime, mtime, btime + entries += 3; + } + if (details & KIO::Acl) { // acl data - entry.reserve(13); - break; - default: // case details > 2 + entries += 3; + } + if (details & KIO::Inode) { // dev, inode - entry.reserve(15); - break; + entries += 2; + } + entry.reserve(entries); + + if (details & KIO::Basic) { + entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); } - entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); mode_t type; mode_t access; @@ -938,64 +978,68 @@ QT_STATBUF buff; #endif - if (LSTAT(path.data(), &buff) == 0) { + if (LSTAT(path.data(), &buff, details) == 0) { - if (details > 2) { + if (details & KIO::Inode) { entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff)); entry.fastInsert(KIO::UDSEntry::UDS_INODE, stat_ino(buff)); } if ((stat_mode(buff) & QT_STAT_MASK) == QT_STAT_LNK) { + if (details & KIO::Basic) { #ifdef Q_OS_WIN const QString linkTarget = QFile::symLinkTarget(QFile::decodeName(path)); #else - // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) - #if HAVE_STATX - size_t lowerBound = 256; - size_t higherBound = 1024; - uint64_t s = stat_size(buff); - if (s > SIZE_MAX) { - qCWarning(KIO_FILE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path; - return false; - } - size_t size = static_cast(s); - using SizeType = size_t; - #else - off_t lowerBound = 256; - off_t higherBound = 1024; - off_t size = stat_size(buff); - using SizeType = off_t; - #endif - SizeType bufferSize = qBound(lowerBound, size +1, higherBound); - QByteArray linkTargetBuffer; - linkTargetBuffer.resize(bufferSize); - while (true) { - ssize_t n = readlink(path.constData(), linkTargetBuffer.data(), bufferSize); - if (n < 0 && errno != ERANGE) { - qCWarning(KIO_FILE) << "readlink failed!" << path; - return false; - } else if (n > 0 && static_cast(n) != bufferSize) { - // the buffer was not filled in the last iteration - // we are finished reading, break the loop - linkTargetBuffer.truncate(n); - break; - } - bufferSize *= 2; + // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) + #if HAVE_STATX + size_t lowerBound = 256; + size_t higherBound = 1024; + uint64_t s = stat_size(buff); + if (s > SIZE_MAX) { + qCWarning(KIO_FILE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path; + return false; + } + size_t size = static_cast(s); + using SizeType = size_t; + #else + off_t lowerBound = 256; + off_t higherBound = 1024; + off_t size = stat_size(buff); + using SizeType = off_t; + #endif + SizeType bufferSize = qBound(lowerBound, size +1, higherBound); + QByteArray linkTargetBuffer; linkTargetBuffer.resize(bufferSize); - } - const QString linkTarget = QFile::decodeName(linkTargetBuffer); + while (true) { + ssize_t n = readlink(path.constData(), linkTargetBuffer.data(), bufferSize); + if (n < 0 && errno != ERANGE) { + qCWarning(KIO_FILE) << "readlink failed!" << path; + return false; + } else if (n > 0 && static_cast(n) != bufferSize) { + // the buffer was not filled in the last iteration + // we are finished reading, break the loop + linkTargetBuffer.truncate(n); + break; + } + bufferSize *= 2; + linkTargetBuffer.resize(bufferSize); + } + const QString linkTarget = QFile::decodeName(linkTargetBuffer); #endif - entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget); + entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget); + } - // A symlink -> follow it only if details>1 - if (details > 1) { - if (STAT(path.constData(), &buff) == -1) { + // A symlink + if (details & KIO::ResolveSymlink) { + if (STAT(path.constData(), &buff, details) == -1) { isBrokenSymLink = true; } else { #if HAVE_POSIX_ACL - // valid symlink, will get the ACLs of the destination - targetPath = linkTargetBuffer; + if (details & KIO::Acl) { + // valid symlink, will get the ACLs of the destination + targetPath = linkTargetBuffer; + } #endif } } @@ -1005,39 +1049,44 @@ return false; } - if (isBrokenSymLink) { - // It is a link pointing to nowhere - type = S_IFMT - 1; - access = S_IRWXU | S_IRWXG | S_IRWXO; - size = 0LL; - } else { - type = stat_mode(buff) & S_IFMT; // extract file type - access = stat_mode(buff) & 07777; // extract permissions - size = stat_size(buff); - } + if (details & KIO::Basic) { + if (isBrokenSymLink) { + // It is a link pointing to nowhere + type = S_IFMT - 1; + access = S_IRWXU | S_IRWXG | S_IRWXO; + size = 0LL; + } else { + type = stat_mode(buff) & S_IFMT; // extract file type + access = stat_mode(buff) & 07777; // extract permissions + size = stat_size(buff); + } - entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type); - entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); - entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); + entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type); + entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); + entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); + } #if HAVE_POSIX_ACL - if (details > 1) { + if (details & KIO::Acl) { /* Append an atom indicating whether the file has extended acl information * and if withACL is specified also one with the acl itself. If it's a directory * and it has a default ACL, also append that. */ appendACLAtoms(targetPath, entry, type); } #endif - if (details > 0) { - entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff)); - entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff)); + if (details & KIO::User) { #ifndef Q_OS_WIN entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(stat_uid(buff)))); entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(stat_gid(buff)))); #else #pragma message("TODO: st_uid and st_gid are always zero, use GetSecurityInfo to find the owner") #endif + } + + if (details & KIO::Time) { + entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff)); + entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff)); #ifdef st_birthtime /* For example FreeBSD's and NetBSD's stat contains a field for diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cpp --- a/src/ioslaves/file/file_unix.cpp +++ b/src/ioslaves/file/file_unix.cpp @@ -48,6 +48,7 @@ #include #include "fdreceiver.h" +#include "statjob.h" //sendfile has different semantics in different platforms #if HAVE_SENDFILE && defined Q_OS_LINUX @@ -603,8 +604,7 @@ return; } - const QString sDetails = metaData(QStringLiteral("details")); - const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); + const KIO::StatDetails details = getStatDetails(); //qDebug() << "========= LIST " << url << "details=" << details << " ========="; UDSEntry entry; @@ -627,7 +627,7 @@ * for every entry thus becoming slower. * */ - if (details == 0) { + if (details == KIO::Basic) { entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); #ifdef HAVE_DIRENT_D_TYPE entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, @@ -892,6 +892,25 @@ } } +KIO::StatDetails FileProtocol::getStatDetails() +{ + // takes care of converting old metadata details to new StatDetails + // TODO KF6 : remove legacy "details" code path + KIO::StatDetails details; +#if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69) + if (hasMetaData(QStringLiteral("statDetails"))) { +#endif + const QString statDetails = metaData(QStringLiteral("statDetails")); + details = statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast(statDetails.toInt()); +#if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69) + } else { + const QString sDetails = metaData(QStringLiteral("details")); + details = sDetails.isEmpty() ? KIO::StatDefaultDetails : KIO::detailsToStatDetails(sDetails.toInt()); + } +#endif + return details; +} + void FileProtocol::stat(const QUrl &url) { if (!isLocalFileSameHost(url)) { @@ -908,8 +927,8 @@ */ const QString path(url.adjusted(QUrl::StripTrailingSlash).toLocalFile()); const QByteArray _path(QFile::encodeName(path)); - const QString sDetails = metaData(QStringLiteral("details")); - const int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); + + const KIO::StatDetails details = getStatDetails(); UDSEntry entry; if (!createUDSEntry(url.fileName(), _path, entry, details)) { diff --git a/src/widgets/krun.cpp b/src/widgets/krun.cpp --- a/src/widgets/krun.cpp +++ b/src/widgets/krun.cpp @@ -941,7 +941,7 @@ // It may be a directory or a file, let's stat KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; - KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags); + KIO::StatJob *job = KIO::statDetails(d->m_strURL, KIO::StatJob::SourceSide, KIO::StatDetail::Basic, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, &KJob::result, this, &KRun::slotStatResult); diff --git a/src/widgets/paste.cpp b/src/widgets/paste.cpp --- a/src/widgets/paste.cpp +++ b/src/widgets/paste.cpp @@ -77,7 +77,7 @@ static QUrl getDestinationUrl(const QUrl &srcUrl, const QUrl &destUrl, QWidget *widget) { KIO::StatJob *job = KIO::stat(destUrl, destUrl.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags); - job->setDetails(0); + job->setDetails(KIO::StatDetail::Basic); job->setSide(KIO::StatJob::DestinationSide); KJobWidgets::setWindow(job, widget); diff --git a/tests/kioslavetest.cpp b/tests/kioslavetest.cpp --- a/tests/kioslavetest.cpp +++ b/tests/kioslavetest.cpp @@ -249,7 +249,7 @@ break; case Stat: - myJob = KIO::stat(src, KIO::StatJob::SourceSide, 2); + myJob = KIO::statDetails(src, KIO::StatJob::SourceSide); break; case Get: diff --git a/tests/listjobtest.cpp b/tests/listjobtest.cpp --- a/tests/listjobtest.cpp +++ b/tests/listjobtest.cpp @@ -41,7 +41,7 @@ KIO::ListJob *job = KIO::listDir(url, KIO::HideProgressInfo); job->setUiDelegate(nullptr); - job->addMetaData(QStringLiteral("details"), QStringLiteral("2")); // Default is 2 which means all details. 0 means just a few essential fields (KIO::UDSEntry::UDS_NAME, KIO::UDSEntry::UDS_FILE_TYPE and KIO::UDSEntry::UDS_LINK_DEST if it is a symbolic link. Not provided otherwise. + job->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatDefaultDetails)); QObject::connect(job, &KIO::ListJob::entries, [&entriesListed] (KIO::Job*, const KIO::UDSEntryList &entries) {