From c23151d1b0a5b41fbd62b3ce7fc68a08b4d7b515 Mon Sep 17 00:00:00 2001 From: Zhang Sheng Date: Tue, 21 Apr 2026 16:21:52 +0800 Subject: [PATCH 1/2] refactor: optimize file sorting implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Refactor file sorting logic into dedicated DFileSorter class with SortRole enum and SortConfig struct 2. Implement DSortKeyCache for performance optimization of string comparisons 3. Add thread-safe QCollator usage with thread_local storage 4. Simplify enumerator code by delegating sorting to DFileSorter 5. Add comprehensive unit tests covering sorting by name, size, time etc. 6. Support both mixed and separated directory/file sorting modes 7. Implement natural number sorting via QCollator numeric mode Log: Improved file sorting performance and maintainability Influence: 1. Test various sorting combinations (name/size/time ascending/ descending) 2. Verify mixed vs separated directory/file sorting modes 3. Check natural number sorting (e.g. file2 < file10) 4. Test edge cases (empty lists, single item) 5. Verify thread safety with concurrent sorting 6. Check localized string sorting behavior refactor: 优化文件排序实现 1. 将文件排序逻辑重构为专用的 DFileSorter 类,包含 SortRole 枚举和 SortConfig 结构体 2. 实现 DSortKeyCache 用于字符串比较的性能优化 3. 使用 thread_local 存储实现线程安全的 QCollator 4. 简化枚举器代码,将排序委托给 DFileSorter 5. 添加全面的单元测试,覆盖按名称、大小、时间等排序 6. 支持混合和分离的目录/文件排序模式 7. 通过 QCollator 数字模式实现自然数字排序 Log: 提升文件排序性能和可维护性 Influence: 1. 测试各种排序组合(名称/大小/时间 升序/降序) 2. 验证混合与分离的目录/文件排序模式 3. 检查自然数字排序(例如 file2 < file10) 4. 测试边界情况(空列表、单项) 5. 验证并发排序时的线程安全性 6. 检查本地化字符串排序行为 --- autotests/dfm-io-tests/CMakeLists.txt | 32 +- autotests/dfm-io-tests/test_main.cpp | 23 ++ autotests/dfm-io-tests/tst_dfilesorter.cpp | 396 +++++++++++++++++++++ autotests/dfm-io-tests/tst_dfm_io.cpp | 7 +- src/dfm-io/dfm-io/denumerator.cpp | 53 ++- src/dfm-io/dfm-io/sort/dfilesorter.cpp | 153 ++++++++ src/dfm-io/dfm-io/sort/dfilesorter.h | 139 ++++++++ src/dfm-io/dfm-io/sort/dsortkeycache.cpp | 60 ++++ src/dfm-io/dfm-io/sort/dsortkeycache.h | 75 ++++ 9 files changed, 908 insertions(+), 30 deletions(-) create mode 100644 autotests/dfm-io-tests/test_main.cpp create mode 100644 autotests/dfm-io-tests/tst_dfilesorter.cpp create mode 100644 src/dfm-io/dfm-io/sort/dfilesorter.cpp create mode 100644 src/dfm-io/dfm-io/sort/dfilesorter.h create mode 100644 src/dfm-io/dfm-io/sort/dsortkeycache.cpp create mode 100644 src/dfm-io/dfm-io/sort/dsortkeycache.h diff --git a/autotests/dfm-io-tests/CMakeLists.txt b/autotests/dfm-io-tests/CMakeLists.txt index e93283f6..3336d999 100644 --- a/autotests/dfm-io-tests/CMakeLists.txt +++ b/autotests/dfm-io-tests/CMakeLists.txt @@ -9,15 +9,21 @@ endif() message(STATUS "Adding unit tests for ${IO_TEST_LIB}") -# Collect test source files -file(GLOB_RECURSE TEST_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/*.h +# 共享的测试入口点 +set(TEST_COMMON_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp +) + +# 各测试类源文件(不含 main) +set(TEST_CLASS_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/tst_dfm_io.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tst_dfilesorter.cpp ) # Create the test executable add_executable(dfm-io-test - ${TEST_SRCS} + ${TEST_COMMON_SRCS} + ${TEST_CLASS_SRCS} ) # Link against the dfm-io library and Qt Test @@ -27,17 +33,11 @@ target_link_libraries(dfm-io-test ) # Add include directories -if(DFM_BUILD_WITH_QT6) - target_include_directories(dfm-io-test - PRIVATE - ${CMAKE_SOURCE_DIR}/src/dfm-io/dfm-io - ) -else() - target_include_directories(dfm-io-test - PRIVATE - ${CMAKE_SOURCE_DIR}/src/dfm-io/dfm-io - ) -endif() +target_include_directories(dfm-io-test + PRIVATE + ${CMAKE_SOURCE_DIR}/src/dfm-io/dfm-io + ${CMAKE_SOURCE_DIR}/src/dfm-io/dfm-io/sort +) # Register the test with CTest add_test(NAME dfm-io-test COMMAND dfm-io-test) diff --git a/autotests/dfm-io-tests/test_main.cpp b/autotests/dfm-io-tests/test_main.cpp new file mode 100644 index 00000000..1432aae5 --- /dev/null +++ b/autotests/dfm-io-tests/test_main.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +// 前置声明测试类 +class tst_DfmIO; +class tst_DFileSorter; + +int main(int argc, char *argv[]) +{ + int status = 0; + + // 运行各测试类 + extern int run_tst_DfmIO(int argc, char *argv[]); + extern int run_tst_DFileSorter(int argc, char *argv[]); + + status |= run_tst_DfmIO(argc, argv); + status |= run_tst_DFileSorter(argc, argv); + + return status; +} diff --git a/autotests/dfm-io-tests/tst_dfilesorter.cpp b/autotests/dfm-io-tests/tst_dfilesorter.cpp new file mode 100644 index 00000000..e6c9d9da --- /dev/null +++ b/autotests/dfm-io-tests/tst_dfilesorter.cpp @@ -0,0 +1,396 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "sort/dfilesorter.h" +#include "sort/dsortkeycache.h" + +using namespace dfmio; + +class tst_DFileSorter : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + // DSortKeyCache 测试 + void sortKeyCache_basic(); + void sortKeyCache_numericMode(); + void sortKeyCache_threadSafety(); + + // DFileSorter 测试 + void fileSorter_sortByName(); + void fileSorter_sortByName_descending(); + void fileSorter_sortBySize(); + void fileSorter_sortByLastModified(); + void fileSorter_sortByLastRead(); + void fileSorter_mixDirAndFile(); + void fileSorter_separateDirAndFile(); + void fileSorter_emptyList(); + void fileSorter_singleItem(); +}; + +void tst_DFileSorter::initTestCase() +{ +} + +void tst_DFileSorter::cleanupTestCase() +{ +} + +// ==================== DSortKeyCache 测试 ==================== + +void tst_DFileSorter::sortKeyCache_basic() +{ + DSortKeyCache &cache = DSortKeyCache::instance(); + + // 相同字符串应返回相同的排序键 + QCollatorSortKey key1 = cache.sortKey("test"); + QCollatorSortKey key2 = cache.sortKey("test"); + QCOMPARE(key1.compare(key2), 0); +} + +void tst_DFileSorter::sortKeyCache_numericMode() +{ + DSortKeyCache &cache = DSortKeyCache::instance(); + + // 数字自然排序:file2 < file10 + QCollatorSortKey key2 = cache.sortKey("file2"); + QCollatorSortKey key10 = cache.sortKey("file10"); + + // file2 应该排在 file10 前面(numericMode 开启) + QVERIFY(key2.compare(key10) < 0); +} + +void tst_DFileSorter::sortKeyCache_threadSafety() +{ + // 测试 thread_local QCollator 的线程安全性 + // 每个线程应该有独立的 QCollator 实例 + DSortKeyCache &cache = DSortKeyCache::instance(); + + // 验证排序键可以正常比较(不假设大小写不敏感) + QCollatorSortKey key1 = cache.sortKey("abc"); + + // 相同字符串返回相同排序键 + QCollatorSortKey key2 = cache.sortKey("abc"); + QCOMPARE(key1.compare(key2), 0); +} + +// ==================== DFileSorter 测试 ==================== + +void tst_DFileSorter::fileSorter_sortByName() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Name; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + // 创建测试文件列表 + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/file10.txt"); + file1->isDir = false; + file1->filesize = 100; + files.append(file1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/file2.txt"); + file2->isDir = false; + file2->filesize = 200; + files.append(file2); + + auto file3 = QSharedPointer::create(); + file3->url = QUrl::fromLocalFile("/test/file1.txt"); + file3->isDir = false; + file3->filesize = 300; + files.append(file3); + + auto sorted = sorter.sort(std::move(files)); + + // 验证排序结果:file1 < file2 < file10(自然排序) + QCOMPARE(sorted.size(), 3); + QCOMPARE(sorted[0]->url.fileName(), QString("file1.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("file2.txt")); + QCOMPARE(sorted[2]->url.fileName(), QString("file10.txt")); +} + +void tst_DFileSorter::fileSorter_sortByName_descending() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Name; + config.order = Qt::DescendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/a.txt"); + file1->isDir = false; + files.append(file1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/b.txt"); + file2->isDir = false; + files.append(file2); + + auto file3 = QSharedPointer::create(); + file3->url = QUrl::fromLocalFile("/test/c.txt"); + file3->isDir = false; + files.append(file3); + + auto sorted = sorter.sort(std::move(files)); + + // 降序:c > b > a + QCOMPARE(sorted.size(), 3); + QCOMPARE(sorted[0]->url.fileName(), QString("c.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("b.txt")); + QCOMPARE(sorted[2]->url.fileName(), QString("a.txt")); +} + +void tst_DFileSorter::fileSorter_sortBySize() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Size; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/large.txt"); + file1->isDir = false; + file1->filesize = 1000; + files.append(file1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/small.txt"); + file2->isDir = false; + file2->filesize = 100; + files.append(file2); + + auto file3 = QSharedPointer::create(); + file3->url = QUrl::fromLocalFile("/test/medium.txt"); + file3->isDir = false; + file3->filesize = 500; + files.append(file3); + + auto sorted = sorter.sort(std::move(files)); + + // 按大小升序:small < medium < large + QCOMPARE(sorted.size(), 3); + QCOMPARE(sorted[0]->url.fileName(), QString("small.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("medium.txt")); + QCOMPARE(sorted[2]->url.fileName(), QString("large.txt")); +} + +void tst_DFileSorter::fileSorter_sortByLastModified() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::LastModified; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/newest.txt"); + file1->isDir = false; + file1->lastModifed = 3000; + file1->lastModifedNs = 0; + files.append(file1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/oldest.txt"); + file2->isDir = false; + file2->lastModifed = 1000; + file2->lastModifedNs = 0; + files.append(file2); + + auto file3 = QSharedPointer::create(); + file3->url = QUrl::fromLocalFile("/test/middle.txt"); + file3->isDir = false; + file3->lastModifed = 2000; + file3->lastModifedNs = 0; + files.append(file3); + + auto sorted = sorter.sort(std::move(files)); + + // 按修改时间升序:oldest < middle < newest + QCOMPARE(sorted.size(), 3); + QCOMPARE(sorted[0]->url.fileName(), QString("oldest.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("middle.txt")); + QCOMPARE(sorted[2]->url.fileName(), QString("newest.txt")); +} + +void tst_DFileSorter::fileSorter_sortByLastRead() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::LastRead; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/read_newest.txt"); + file1->isDir = false; + file1->lastRead = 3000; + file1->lastReadNs = 0; + files.append(file1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/read_oldest.txt"); + file2->isDir = false; + file2->lastRead = 1000; + file2->lastReadNs = 0; + files.append(file2); + + auto sorted = sorter.sort(std::move(files)); + + // 按访问时间升序:oldest < newest + QCOMPARE(sorted.size(), 2); + QCOMPARE(sorted[0]->url.fileName(), QString("read_oldest.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("read_newest.txt")); +} + +void tst_DFileSorter::fileSorter_mixDirAndFile() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Name; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; // 混排模式 + + DFileSorter sorter(config); + + QList> files; + + auto dir1 = QSharedPointer::create(); + dir1->url = QUrl::fromLocalFile("/test/docs"); + dir1->isDir = true; + dir1->isSymLink = false; + files.append(dir1); + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/afile.txt"); + file1->isDir = false; + files.append(file1); + + auto dir2 = QSharedPointer::create(); + dir2->url = QUrl::fromLocalFile("/test/apps"); + dir2->isDir = true; + dir2->isSymLink = false; + files.append(dir2); + + auto sorted = sorter.sort(std::move(files)); + + // 混排模式:所有项一起排序 + QCOMPARE(sorted.size(), 3); + QCOMPARE(sorted[0]->url.fileName(), QString("afile.txt")); // a 排最前 + QCOMPARE(sorted[1]->url.fileName(), QString("apps")); + QCOMPARE(sorted[2]->url.fileName(), QString("docs")); +} + +void tst_DFileSorter::fileSorter_separateDirAndFile() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Name; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = false; // 分离模式:目录在前 + + DFileSorter sorter(config); + + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/a_file.txt"); + file1->isDir = false; + files.append(file1); + + auto dir1 = QSharedPointer::create(); + dir1->url = QUrl::fromLocalFile("/test/z_dir"); + dir1->isDir = true; + dir1->isSymLink = false; + files.append(dir1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/b_file.txt"); + file2->isDir = false; + files.append(file2); + + auto dir2 = QSharedPointer::create(); + dir2->url = QUrl::fromLocalFile("/test/a_dir"); + dir2->isDir = true; + dir2->isSymLink = false; + files.append(dir2); + + auto sorted = sorter.sort(std::move(files)); + + // 分离模式:目录在前(按名称排序),文件在后(按名称排序) + QCOMPARE(sorted.size(), 4); + // 目录在前 + QCOMPARE(sorted[0]->url.fileName(), QString("a_dir")); + QCOMPARE(sorted[1]->url.fileName(), QString("z_dir")); + // 文件在后 + QCOMPARE(sorted[2]->url.fileName(), QString("a_file.txt")); + QCOMPARE(sorted[3]->url.fileName(), QString("b_file.txt")); +} + +void tst_DFileSorter::fileSorter_emptyList() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Name; + + DFileSorter sorter(config); + + QList> files; + auto sorted = sorter.sort(std::move(files)); + + QVERIFY(sorted.isEmpty()); +} + +void tst_DFileSorter::fileSorter_singleItem() +{ + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Name; + + DFileSorter sorter(config); + + QList> files; + + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/single.txt"); + file1->isDir = false; + files.append(file1); + + auto sorted = sorter.sort(std::move(files)); + + QCOMPARE(sorted.size(), 1); + QCOMPARE(sorted[0]->url.fileName(), QString("single.txt")); +} + +int run_tst_DFileSorter(int argc, char *argv[]) +{ + tst_DFileSorter tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_dfilesorter.moc" + diff --git a/autotests/dfm-io-tests/tst_dfm_io.cpp b/autotests/dfm-io-tests/tst_dfm_io.cpp index da783bca..17b33637 100644 --- a/autotests/dfm-io-tests/tst_dfm_io.cpp +++ b/autotests/dfm-io-tests/tst_dfm_io.cpp @@ -30,5 +30,10 @@ void tst_DfmIO::initialization_test() QVERIFY(true); } -QTEST_MAIN(tst_DfmIO) +int run_tst_DfmIO(int argc, char *argv[]) +{ + tst_DfmIO tc; + return QTest::qExec(&tc, argc, argv); +} + #include "tst_dfm_io.moc" diff --git a/src/dfm-io/dfm-io/denumerator.cpp b/src/dfm-io/dfm-io/denumerator.cpp index 6c129566..bc14e046 100644 --- a/src/dfm-io/dfm-io/denumerator.cpp +++ b/src/dfm-io/dfm-io/denumerator.cpp @@ -5,6 +5,7 @@ #include "private/denumerator_p.h" #include "utils/dlocalhelper.h" +#include "sort/dfilesorter.h" #include #include @@ -803,14 +804,29 @@ QList> DEnumerator::fileInfoList() QList> DEnumerator::sortFileInfoList() { - if (!d->fts) - d->openDirByfts(); + // 使用 FTS 遍历但不预排序(传 nullptr 作为比较函数) + if (!d->fts) { + char *paths[2] = { nullptr, nullptr }; + paths[0] = d->filePath(d->uri); + if (!paths[0]) { + qWarning() << "Failed to get file path for uri:" << d->uri; + return {}; + } + d->fts = fts_open(paths, FTS_COMFOLLOW, nullptr); + free(paths[0]); + + if (!d->fts) { + qWarning() << "fts_open open error : " << QString::fromLocal8Bit(strerror(errno)); + d->error.setCode(DFMIOErrorCode::DFM_IO_ERROR_FTS_OPEN); + return {}; + } + } if (!d->fts) return {}; - QList> listFile; - QList> listDir; + // 收集所有文件信息 + QList> allFiles; QSet hideList; QUrl urlHidden = d->buildUrl(d->uri, ".hidden"); hideList = DLocalHelper::hideListFromUrl(urlHidden); @@ -819,7 +835,8 @@ QList> DEnumerator::sortFileInfoList() qWarning() << "Failed to get file path for uri:" << d->uri; return {}; } - while (1) { + + while (true) { FTSENT *ent = fts_read(d->fts); if (ent == nullptr) { @@ -834,20 +851,30 @@ QList> DEnumerator::sortFileInfoList() if (strcmp(ent->fts_path, dirPath) == 0 || flag == FTS_DP) continue; - d->insertSortFileInfoList(listFile, listDir, ent, d->fts, hideList); + auto sortInfo = DLocalHelper::createSortFileInfo(ent, hideList); + // 跳过子目录遍历 + if (sortInfo->isDir && !sortInfo->isSymLink) { + fts_set(d->fts, ent, FTS_SKIP); + } + allFiles.append(sortInfo); } fts_close(d->fts); d->fts = nullptr; - - // Clean up allocated memory free(dirPath); - if (d->isMixDirAndFile) - return listFile; - - listDir.append(listFile); - return listDir; + // 使用 DFileSorter 进行排序 + DFileSorter::SortConfig config; + // 映射排序角色:kSortRoleCompareFileName(1) -> Name(0), 以此类推 + // kSortRoleCompareDefault(0) 默认按名称排序 + config.role = (d->sortRoleFlag == SortRoleCompareFlag::kSortRoleCompareDefault) + ? DFileSorter::SortRole::Name + : static_cast(static_cast(d->sortRoleFlag) - 1); + config.order = d->sortOrder; + config.mixDirAndFile = d->isMixDirAndFile; + + DFileSorter sorter(config); + return sorter.sort(std::move(allFiles)); } DFMIOError DEnumerator::lastError() const diff --git a/src/dfm-io/dfm-io/sort/dfilesorter.cpp b/src/dfm-io/dfm-io/sort/dfilesorter.cpp new file mode 100644 index 00000000..49f511b3 --- /dev/null +++ b/src/dfm-io/dfm-io/sort/dfilesorter.cpp @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dfilesorter.h" +#include "dsortkeycache.h" + +#include + +USING_IO_NAMESPACE + +DFileSorter::DFileSorter(const SortConfig &config) + : m_config(config) +{ +} + +QList> DFileSorter::sort( + QList> &&files) +{ + if (files.isEmpty()) { + return files; + } + + QList> result; + + if (m_config.mixDirAndFile) { + // 混排模式:所有文件一起排序 + std::stable_sort(files.begin(), files.end(), getCompareFunc()); + applySortOrder(files); + result = std::move(files); + } else { + // 分离模式:目录在前,文件在后,分别排序 + QList> dirs; + QList> regularFiles; + + separateDirAndFile(std::move(files), dirs, regularFiles); + + std::stable_sort(dirs.begin(), dirs.end(), getCompareFunc()); + applySortOrder(dirs); + + std::stable_sort(regularFiles.begin(), regularFiles.end(), getCompareFunc()); + applySortOrder(regularFiles); + + // 目录排在前面 + result = std::move(dirs); + result.append(std::move(regularFiles)); + } + + return result; +} + +QList> DFileSorter::sort( + const QList> &files) +{ + // 拷贝后排序 + QList> copy = files; + return sort(std::move(copy)); +} + +DFileSorter::CompareFunc DFileSorter::getCompareFunc() const +{ + switch (m_config.role) { + case SortRole::Name: + return [this](const auto &a, const auto &b) { return compareByName(a, b); }; + case SortRole::Size: + return [this](const auto &a, const auto &b) { return compareBySize(a, b); }; + case SortRole::LastModified: + return [this](const auto &a, const auto &b) { return compareByLastModified(a, b); }; + case SortRole::LastRead: + return [this](const auto &a, const auto &b) { return compareByLastRead(a, b); }; + default: + return [this](const auto &a, const auto &b) { return compareByName(a, b); }; + } +} + +bool DFileSorter::compareByName( + const QSharedPointer &a, + const QSharedPointer &b) const +{ + // 使用预缓存的排序键进行比较 + QString nameA = a->url.fileName(); + QString nameB = b->url.fileName(); + + QCollatorSortKey keyA = DSortKeyCache::instance().sortKey(nameA); + QCollatorSortKey keyB = DSortKeyCache::instance().sortKey(nameB); + + return keyA < keyB; +} + +bool DFileSorter::compareBySize( + const QSharedPointer &a, + const QSharedPointer &b) const +{ + // 先按大小比较,大小相同时按名称排序 + if (a->filesize != b->filesize) { + return a->filesize < b->filesize; + } + return compareByName(a, b); +} + +bool DFileSorter::compareByLastModified( + const QSharedPointer &a, + const QSharedPointer &b) const +{ + // 先比较秒,再比较纳秒 + if (a->lastModifed != b->lastModifed) { + return a->lastModifed < b->lastModifed; + } + if (a->lastModifedNs != b->lastModifedNs) { + return a->lastModifedNs < b->lastModifedNs; + } + // 时间相同,按名称排序 + return compareByName(a, b); +} + +bool DFileSorter::compareByLastRead( + const QSharedPointer &a, + const QSharedPointer &b) const +{ + // 先比较秒,再比较纳秒 + if (a->lastRead != b->lastRead) { + return a->lastRead < b->lastRead; + } + if (a->lastReadNs != b->lastReadNs) { + return a->lastReadNs < b->lastReadNs; + } + // 时间相同,按名称排序 + return compareByName(a, b); +} + +void DFileSorter::separateDirAndFile( + QList> &&files, + QList> &dirs, + QList> ®ularFiles) const +{ + dirs.reserve(files.size()); + regularFiles.reserve(files.size()); + + for (auto &file : files) { + if (file->isDir && !file->isSymLink) { + dirs.append(std::move(file)); + } else { + regularFiles.append(std::move(file)); + } + } +} + +void DFileSorter::applySortOrder(QList> &list) const +{ + if (m_config.order == Qt::DescendingOrder) { + std::reverse(list.begin(), list.end()); + } +} diff --git a/src/dfm-io/dfm-io/sort/dfilesorter.h b/src/dfm-io/dfm-io/sort/dfilesorter.h new file mode 100644 index 00000000..09067af9 --- /dev/null +++ b/src/dfm-io/dfm-io/sort/dfilesorter.h @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DFILESORTER_H +#define DFILESORTER_H + +#include +#include + +#include +#include + +#include + +BEGIN_IO_NAMESPACE + +/** + * @brief 文件排序器 + * + * 提供高性能的文件列表排序功能,支持多种排序角色和排序顺序。 + * 使用 QCollator::sortKey() 进行预缓存排序键,优化性能。 + * + * 用法示例: + * @code + * DFileSorter::SortConfig config; + * config.role = DFileSorter::SortRole::Name; + * config.order = Qt::AscendingOrder; + * config.mixDirAndFile = false; + * + * DFileSorter sorter(config); + * auto sortedList = sorter.sort(std::move(fileList)); + * @endcode + */ +class DFileSorter +{ +public: + /** + * @brief 排序角色 + */ + enum class SortRole : uint8_t { + Name = 0, // 按文件名排序 + Size = 1, // 按文件大小排序 + LastModified = 2, // 按最后修改时间排序 + LastRead = 3 // 按最后访问时间排序 + }; + + /** + * @brief 排序配置 + */ + struct SortConfig { + SortRole role { SortRole::Name }; // 排序角色 + Qt::SortOrder order { Qt::AscendingOrder }; // 排序顺序 + bool mixDirAndFile { false }; // 是否混排目录和文件 + }; + + /** + * @brief 构造函数 + * + * @param config 排序配置 + */ + explicit DFileSorter(const SortConfig &config); + + /** + * @brief 对文件列表进行排序 + * + * @param files 文件列表(使用移动语义避免拷贝) + * @return 排序后的文件列表 + */ + QList> sort( + QList> &&files); + + /** + * @brief 对文件列表进行排序(拷贝版本) + * + * @param files 文件列表 + * @return 排序后的文件列表 + */ + QList> sort( + const QList> &files); + +private: + using CompareFunc = std::function &, + const QSharedPointer &)>; + + /** + * @brief 获取排序比较函数 + */ + CompareFunc getCompareFunc() const; + + /** + * @brief 按名称比较 + */ + bool compareByName( + const QSharedPointer &a, + const QSharedPointer &b) const; + + /** + * @brief 按大小比较 + */ + bool compareBySize( + const QSharedPointer &a, + const QSharedPointer &b) const; + + /** + * @brief 按最后修改时间比较 + */ + bool compareByLastModified( + const QSharedPointer &a, + const QSharedPointer &b) const; + + /** + * @brief 按最后访问时间比较 + */ + bool compareByLastRead( + const QSharedPointer &a, + const QSharedPointer &b) const; + + /** + * @brief 分离目录和文件 + */ + void separateDirAndFile( + QList> &&files, + QList> &dirs, + QList> ®ularFiles) const; + + /** + * @brief 应用排序顺序 + */ + void applySortOrder(QList> &list) const; + +private: + SortConfig m_config; +}; + +END_IO_NAMESPACE + +#endif // DFILESORTER_H diff --git a/src/dfm-io/dfm-io/sort/dsortkeycache.cpp b/src/dfm-io/dfm-io/sort/dsortkeycache.cpp new file mode 100644 index 00000000..003ee411 --- /dev/null +++ b/src/dfm-io/dfm-io/sort/dsortkeycache.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dsortkeycache.h" + +USING_IO_NAMESPACE + +DSortKeyCache &DSortKeyCache::instance() +{ + static DSortKeyCache instance; + return instance; +} + +QCollatorSortKey DSortKeyCache::sortKey(const QString &text) +{ + // 先尝试读锁查找缓存 + { + QReadLocker locker(&m_lock); + auto it = m_cache.find(text); + if (it != m_cache.end()) { + return *it; + } + } + + // 缓存未命中,需要创建 + QWriteLocker locker(&m_lock); + + // 双重检查,防止其他线程已经创建 + auto it = m_cache.find(text); + if (it != m_cache.end()) { + return *it; + } + + QCollatorSortKey key = collator().sortKey(text); + m_cache.insert(text, key); + return key; +} + +void DSortKeyCache::clear() +{ + QWriteLocker locker(&m_lock); + m_cache.clear(); +} + +QCollator &DSortKeyCache::collator() +{ + // 线程局部存储,每个线程有自己的 QCollator 实例 + // 避免多线程竞争问题 + thread_local static QCollator s_collator = []() { + QCollator c; + c.setNumericMode(true); // 支持数字自然排序,如 "file2" < "file10" + return c; + }(); + return s_collator; +} + +DSortKeyCache::DSortKeyCache() +{ +} diff --git a/src/dfm-io/dfm-io/sort/dsortkeycache.h b/src/dfm-io/dfm-io/sort/dsortkeycache.h new file mode 100644 index 00000000..48923278 --- /dev/null +++ b/src/dfm-io/dfm-io/sort/dsortkeycache.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef DSORTKEYCACHE_H +#define DSORTKEYCACHE_H + +#include + +#include +#include +#include +#include +#include + +BEGIN_IO_NAMESPACE + +/** + * @brief 排序键缓存管理器 + * + * 单例模式,提供线程安全的 QCollatorSortKey 缓存。 + * 使用 QCollator 配置 numericMode 进行国际化自然排序。 + * + * 性能优化: + * - 同一字符串只生成一次排序键 + * - 线程安全的 QCollator 实例(thread_local) + * - 读写锁保护缓存访问 + */ +class DSortKeyCache +{ +public: + /** + * @brief 获取单例实例 + */ + static DSortKeyCache &instance(); + + /** + * @brief 获取或创建字符串的排序键 + * + * 如果缓存中存在则直接返回,否则创建新排序键并缓存。 + * + * @param text 需要排序的文本 + * @return QCollatorSortKey 排序键 + */ + QCollatorSortKey sortKey(const QString &text); + + /** + * @brief 清理缓存 + * + * 在语言环境变化时调用。 + */ + void clear(); + + /** + * @brief 获取配置好的 QCollator 实例 + * + * 线程安全,每个线程有自己的实例。 + * + * @return QCollator& QCollator 引用 + */ + static QCollator &collator(); + +private: + DSortKeyCache(); + ~DSortKeyCache() = default; + DSortKeyCache(const DSortKeyCache &) = delete; + DSortKeyCache &operator=(const DSortKeyCache &) = delete; + + QHash m_cache; + QReadWriteLock m_lock; +}; + +END_IO_NAMESPACE + +#endif // DSORTKEYCACHE_H From 6e97e262659a2888435d01888f80cf27b236cc4f Mon Sep 17 00:00:00 2001 From: Zhang Sheng Date: Tue, 21 Apr 2026 18:34:29 +0800 Subject: [PATCH 2/2] test: enhance file sorting tests with directories and symlinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive test cases for DFileSorter to better handle: 1. Sorting by size with directories (directories should have consistent effective size -1) 2. Size sorting in descending order (directories move to end) 3. Last modified time sorting with symlinks (use target file time) 4. Last read time sorting with symlinks (use target file time) The changes include new unit tests that verify: - Directory sorting behavior in size comparisons - Correct ordering in descending size sort - Proper handling of symbolic links when sorting by time attributes - Mixing of directories and files in different sort scenarios Influence: 1. Test file sorting by size with directories and mixed files 2. Verify descending size sorting places directories correctly 3. Check symlink time sorting uses target file times 4. Validate directory/file separation maintains correct order 5. Test various combinations of the above scenarios with different file sets test: 增强文件排序测试,支持目录和符号链接特殊情况 新增了针对DFileSorter的全面测试用例,主要涵盖: 1. 包含目录的按大小排序(目录始终使用-1作为有效大小) 2. 大小降序排序(目录应排在最后) 3. 带符号链接的按修改时间排序(使用目标文件时间) 4. 带符号链接的按访问时间排序(使用目标文件时间) 变更内容包括: - 验证目录在大小排序中的行为 - 降序排序时目录的正确位置 - 符号链接时间属性使用目标文件时间的处理 - 混合目录和文件在不同排序场景中的表现 Influence: 1. 测试包含目录的按大小文件排序 2. 验证降序大小排序中目录的位置是否正确 3. 检查符号链接时间排序是否使用目标文件时间 4. 验证目录和文件分离时是否保持正确顺序 5. 测试不同文件组合下的各种排序场景 --- autotests/dfm-io-tests/tst_dfilesorter.cpp | 244 +++++++++++++++++++++ src/dfm-io/dfm-io/sort/dfilesorter.cpp | 128 ++++++++--- 2 files changed, 347 insertions(+), 25 deletions(-) diff --git a/autotests/dfm-io-tests/tst_dfilesorter.cpp b/autotests/dfm-io-tests/tst_dfilesorter.cpp index e6c9d9da..5308873a 100644 --- a/autotests/dfm-io-tests/tst_dfilesorter.cpp +++ b/autotests/dfm-io-tests/tst_dfilesorter.cpp @@ -6,6 +6,10 @@ #include #include #include +#include +#include +#include +#include #include "sort/dfilesorter.h" #include "sort/dsortkeycache.h" @@ -29,12 +33,16 @@ private Q_SLOTS: void fileSorter_sortByName(); void fileSorter_sortByName_descending(); void fileSorter_sortBySize(); + void fileSorter_sortBySize_withDirs(); + void fileSorter_sortBySize_descending(); void fileSorter_sortByLastModified(); void fileSorter_sortByLastRead(); void fileSorter_mixDirAndFile(); void fileSorter_separateDirAndFile(); void fileSorter_emptyList(); void fileSorter_singleItem(); + void fileSorter_sortByLastModified_withSymlink(); + void fileSorter_sortByLastRead_withSymlink(); }; void tst_DFileSorter::initTestCase() @@ -197,6 +205,117 @@ void tst_DFileSorter::fileSorter_sortBySize() QCOMPARE(sorted[2]->url.fileName(), QString("large.txt")); } +void tst_DFileSorter::fileSorter_sortBySize_withDirs() +{ + // 测试按大小排序时,目录(有效大小 -1)始终排在前面(升序时) + // 这与原 DLocalHelper::fileSizeByEnt 的行为一致 + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Size; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; // 混排模式 + + DFileSorter sorter(config); + + QList> files; + + // 目录:大小通常为 4096,但有效大小应为 -1 + auto dir1 = QSharedPointer::create(); + dir1->url = QUrl::fromLocalFile("/test/docs"); + dir1->isDir = true; + dir1->isSymLink = false; + dir1->filesize = 4096; // 实际大小 + files.append(dir1); + + // 小文件:100 字节(小于 4096) + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/small.txt"); + file1->isDir = false; + file1->filesize = 100; + files.append(file1); + + // 中等文件:2000 字节(小于 4096) + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/medium.txt"); + file2->isDir = false; + file2->filesize = 2000; + files.append(file2); + + // 大文件:10000 字节(大于 4096) + auto file3 = QSharedPointer::create(); + file3->url = QUrl::fromLocalFile("/test/large.txt"); + file3->isDir = false; + file3->filesize = 10000; + files.append(file3); + + // 另一个目录 + auto dir2 = QSharedPointer::create(); + dir2->url = QUrl::fromLocalFile("/test/apps"); + dir2->isDir = true; + dir2->isSymLink = false; + dir2->filesize = 4096; + files.append(dir2); + + auto sorted = sorter.sort(std::move(files)); + + // 验证:升序时目录(有效大小 -1)在前,然后按文件大小排序 + QCOMPARE(sorted.size(), 5); + // 目录在前(按名称排序,因为大小相同都是 -1) + QCOMPARE(sorted[0]->isDir, true); + QCOMPARE(sorted[0]->url.fileName(), QString("apps")); + QCOMPARE(sorted[1]->isDir, true); + QCOMPARE(sorted[1]->url.fileName(), QString("docs")); + // 文件在后(按大小排序) + QCOMPARE(sorted[2]->isDir, false); + QCOMPARE(sorted[2]->url.fileName(), QString("small.txt")); + QCOMPARE(sorted[3]->isDir, false); + QCOMPARE(sorted[3]->url.fileName(), QString("medium.txt")); + QCOMPARE(sorted[4]->isDir, false); + QCOMPARE(sorted[4]->url.fileName(), QString("large.txt")); +} + +void tst_DFileSorter::fileSorter_sortBySize_descending() +{ + // 测试按大小降序排序 + // 降序时,目录(有效大小 -1)会排到后面 + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::Size; + config.order = Qt::DescendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + QList> files; + + // 目录(有效大小 -1) + auto dir1 = QSharedPointer::create(); + dir1->url = QUrl::fromLocalFile("/test/docs"); + dir1->isDir = true; + dir1->isSymLink = false; + dir1->filesize = 4096; + files.append(dir1); + + // 文件 + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile("/test/small.txt"); + file1->isDir = false; + file1->filesize = 100; + files.append(file1); + + auto file2 = QSharedPointer::create(); + file2->url = QUrl::fromLocalFile("/test/large.txt"); + file2->isDir = false; + file2->filesize = 10000; + files.append(file2); + + auto sorted = sorter.sort(std::move(files)); + + // 降序:大文件 > 小文件 > 目录(-1 最小,反转后在最后) + QCOMPARE(sorted.size(), 3); + QCOMPARE(sorted[0]->url.fileName(), QString("large.txt")); // 大文件 + QCOMPARE(sorted[1]->url.fileName(), QString("small.txt")); // 小文件 + QCOMPARE(sorted[2]->isDir, true); // 目录在最后 +} + void tst_DFileSorter::fileSorter_sortByLastModified() { DFileSorter::SortConfig config; @@ -386,6 +505,131 @@ void tst_DFileSorter::fileSorter_singleItem() QCOMPARE(sorted[0]->url.fileName(), QString("single.txt")); } +void tst_DFileSorter::fileSorter_sortByLastModified_withSymlink() +{ + // 测试按修改时间排序时,符号链接使用其指向文件的时间 + // 需要创建临时文件来测试真实场景 + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::LastModified; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + // 创建临时目录和文件 + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + QString oldFile = tempDir.path() + "/old_file.txt"; + QString newFile = tempDir.path() + "/new_file.txt"; + QString linkFile = tempDir.path() + "/link_to_old.txt"; + + // 创建文件 + { + QFile f(oldFile); + f.open(QIODevice::WriteOnly); + f.setFileTime(QDateTime::fromSecsSinceEpoch(1000), QFileDevice::FileModificationTime); + } + { + QFile f(newFile); + f.open(QIODevice::WriteOnly); + f.setFileTime(QDateTime::fromSecsSinceEpoch(3000), QFileDevice::FileModificationTime); + } + + // 创建符号链接指向旧文件 + QFile::link(oldFile, linkFile); + + QList> files; + + // 普通文件:new_file + auto file1 = QSharedPointer::create(); + file1->url = QUrl::fromLocalFile(newFile); + file1->isDir = false; + file1->isSymLink = false; + file1->lastModifed = 3000; + file1->lastModifedNs = 0; + files.append(file1); + + // 符号链接:指向 old_file(时间 1000) + auto symlink1 = QSharedPointer::create(); + symlink1->url = QUrl::fromLocalFile(linkFile); + symlink1->isDir = false; + symlink1->isSymLink = true; + symlink1->symlinkUrl = QUrl::fromLocalFile(oldFile); // 指向目标文件 + symlink1->lastModifed = 2000; // 链接本身的时间(排序时应忽略,使用目标时间 1000) + symlink1->lastModifedNs = 0; + files.append(symlink1); + + auto sorted = sorter.sort(std::move(files)); + + // 按修改时间升序:link_to_old(1000,目标文件时间) < new_file(3000) + QCOMPARE(sorted.size(), 2); + QCOMPARE(sorted[0]->url.fileName(), QString("link_to_old.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("new_file.txt")); +} + +void tst_DFileSorter::fileSorter_sortByLastRead_withSymlink() +{ + // 测试按访问时间排序时,符号链接使用其指向文件的时间 + DFileSorter::SortConfig config; + config.role = DFileSorter::SortRole::LastRead; + config.order = Qt::AscendingOrder; + config.mixDirAndFile = true; + + DFileSorter sorter(config); + + // 创建临时目录和文件 + QTemporaryDir tempDir; + QVERIFY(tempDir.isValid()); + + QString file1 = tempDir.path() + "/file1.txt"; + QString file2 = tempDir.path() + "/file2.txt"; + QString linkFile = tempDir.path() + "/link_to_file2.txt"; + + // 创建文件并设置访问时间 + { + QFile f(file1); + f.open(QIODevice::WriteOnly); + f.setFileTime(QDateTime::fromSecsSinceEpoch(1000), QFileDevice::FileAccessTime); + } + { + QFile f(file2); + f.open(QIODevice::WriteOnly); + f.setFileTime(QDateTime::fromSecsSinceEpoch(3000), QFileDevice::FileAccessTime); + } + + // 创建符号链接指向 file2 + QFile::link(file2, linkFile); + + QList> files; + + // 普通文件:file1(访问时间 1000) + auto normalFile = QSharedPointer::create(); + normalFile->url = QUrl::fromLocalFile(file1); + normalFile->isDir = false; + normalFile->isSymLink = false; + normalFile->lastRead = 1000; + normalFile->lastReadNs = 0; + files.append(normalFile); + + // 符号链接:指向 file2(访问时间 3000) + auto symlink1 = QSharedPointer::create(); + symlink1->url = QUrl::fromLocalFile(linkFile); + symlink1->isDir = false; + symlink1->isSymLink = true; + symlink1->symlinkUrl = QUrl::fromLocalFile(file2); // 指向目标文件 + symlink1->lastRead = 2000; // 链接本身的时间(排序时应忽略,使用目标时间 3000) + symlink1->lastReadNs = 0; + files.append(symlink1); + + auto sorted = sorter.sort(std::move(files)); + + // 按访问时间升序:file1(1000) < link_to_file2(3000,目标文件时间) + QCOMPARE(sorted.size(), 2); + QCOMPARE(sorted[0]->url.fileName(), QString("file1.txt")); + QCOMPARE(sorted[1]->url.fileName(), QString("link_to_file2.txt")); +} + int run_tst_DFileSorter(int argc, char *argv[]) { tst_DFileSorter tc; diff --git a/src/dfm-io/dfm-io/sort/dfilesorter.cpp b/src/dfm-io/dfm-io/sort/dfilesorter.cpp index 49f511b3..bb1adb17 100644 --- a/src/dfm-io/dfm-io/sort/dfilesorter.cpp +++ b/src/dfm-io/dfm-io/sort/dfilesorter.cpp @@ -6,16 +6,48 @@ #include "dsortkeycache.h" #include +#include USING_IO_NAMESPACE +namespace { + +// 获取符号链接指向文件的时间信息(用于按时间排序) +struct SymlinkTimeInfo { + qint64 lastModified = 0; + qint64 lastModifiedNs = 0; + qint64 lastRead = 0; + qint64 lastReadNs = 0; + bool valid = false; +}; + +SymlinkTimeInfo getSymlinkTargetTime(const QUrl &symlinkUrl) +{ + SymlinkTimeInfo info; + if (!symlinkUrl.isValid() || !symlinkUrl.isLocalFile()) { + return info; + } + + struct stat st; + if (stat(symlinkUrl.path().toUtf8().constData(), &st) == 0) { + info.lastModified = st.st_mtim.tv_sec; + info.lastModifiedNs = st.st_mtim.tv_nsec; + info.lastRead = st.st_atim.tv_sec; + info.lastReadNs = st.st_atim.tv_nsec; + info.valid = true; + } + return info; +} + +} // namespace + DFileSorter::DFileSorter(const SortConfig &config) : m_config(config) { } QList> DFileSorter::sort( - QList> &&files) + QList> &&files) { if (files.isEmpty()) { return files; @@ -50,7 +82,7 @@ QList> DFileSorter::sort( } QList> DFileSorter::sort( - const QList> &files) + const QList> &files) { // 拷贝后排序 QList> copy = files; @@ -74,8 +106,8 @@ DFileSorter::CompareFunc DFileSorter::getCompareFunc() const } bool DFileSorter::compareByName( - const QSharedPointer &a, - const QSharedPointer &b) const + const QSharedPointer &a, + const QSharedPointer &b) const { // 使用预缓存的排序键进行比较 QString nameA = a->url.fileName(); @@ -88,56 +120,102 @@ bool DFileSorter::compareByName( } bool DFileSorter::compareBySize( - const QSharedPointer &a, - const QSharedPointer &b) const + const QSharedPointer &a, + const QSharedPointer &b) const { - // 先按大小比较,大小相同时按名称排序 - if (a->filesize != b->filesize) { - return a->filesize < b->filesize; + // 获取有效大小:目录返回 -1,确保目录始终排在前面 + // 这与原 DLocalHelper::fileSizeByEnt 的行为一致 + auto getEffectiveSize = + [](const QSharedPointer &info) -> qint64 { + // 目录返回 -1 + if (info->isDir) { + return -1; + } + return info->filesize; + }; + + qint64 sizeA = getEffectiveSize(a); + qint64 sizeB = getEffectiveSize(b); + + // 先按有效大小比较,大小相同时按名称排序 + if (sizeA != sizeB) { + return sizeA < sizeB; } return compareByName(a, b); } bool DFileSorter::compareByLastModified( - const QSharedPointer &a, - const QSharedPointer &b) const + const QSharedPointer &a, + const QSharedPointer &b) const { + // 获取用于排序的修改时间:符号链接使用指向文件的时间 + auto getEffectiveModifiedTime = [](const QSharedPointer &info) + -> std::pair { + // 如果是符号链接且有目标 URL,使用目标文件的时间 + if (info->isSymLink && info->symlinkUrl.isValid()) { + auto targetTime = getSymlinkTargetTime(info->symlinkUrl); + if (targetTime.valid) { + return { targetTime.lastModified, targetTime.lastModifiedNs }; + } + } + return { info->lastModifed, info->lastModifedNs }; + }; + + auto [timeA, nsA] = getEffectiveModifiedTime(a); + auto [timeB, nsB] = getEffectiveModifiedTime(b); + // 先比较秒,再比较纳秒 - if (a->lastModifed != b->lastModifed) { - return a->lastModifed < b->lastModifed; + if (timeA != timeB) { + return timeA < timeB; } - if (a->lastModifedNs != b->lastModifedNs) { - return a->lastModifedNs < b->lastModifedNs; + if (nsA != nsB) { + return nsA < nsB; } // 时间相同,按名称排序 return compareByName(a, b); } bool DFileSorter::compareByLastRead( - const QSharedPointer &a, - const QSharedPointer &b) const + const QSharedPointer &a, + const QSharedPointer &b) const { + // 获取用于排序的访问时间:符号链接使用指向文件的时间 + auto getEffectiveReadTime = [](const QSharedPointer &info) + -> std::pair { + // 如果是符号链接且有目标 URL,使用目标文件的时间 + if (info->isSymLink && info->symlinkUrl.isValid()) { + auto targetTime = getSymlinkTargetTime(info->symlinkUrl); + if (targetTime.valid) { + return { targetTime.lastRead, targetTime.lastReadNs }; + } + } + return { info->lastRead, info->lastReadNs }; + }; + + auto [timeA, nsA] = getEffectiveReadTime(a); + auto [timeB, nsB] = getEffectiveReadTime(b); + // 先比较秒,再比较纳秒 - if (a->lastRead != b->lastRead) { - return a->lastRead < b->lastRead; + if (timeA != timeB) { + return timeA < timeB; } - if (a->lastReadNs != b->lastReadNs) { - return a->lastReadNs < b->lastReadNs; + if (nsA != nsB) { + return nsA < nsB; } // 时间相同,按名称排序 return compareByName(a, b); } void DFileSorter::separateDirAndFile( - QList> &&files, - QList> &dirs, - QList> ®ularFiles) const + QList> &&files, + QList> &dirs, + QList> ®ularFiles) const { dirs.reserve(files.size()); regularFiles.reserve(files.size()); for (auto &file : files) { - if (file->isDir && !file->isSymLink) { + if (file->isDir) { dirs.append(std::move(file)); } else { regularFiles.append(std::move(file));