diff --git a/autotests/dfm-io-tests/CMakeLists.txt b/autotests/dfm-io-tests/CMakeLists.txt index 3336d999..e93283f6 100644 --- a/autotests/dfm-io-tests/CMakeLists.txt +++ b/autotests/dfm-io-tests/CMakeLists.txt @@ -9,21 +9,15 @@ endif() message(STATUS "Adding unit tests for ${IO_TEST_LIB}") -# 共享的测试入口点 -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 +# Collect test source files +file(GLOB_RECURSE TEST_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/*.h ) # Create the test executable add_executable(dfm-io-test - ${TEST_COMMON_SRCS} - ${TEST_CLASS_SRCS} + ${TEST_SRCS} ) # Link against the dfm-io library and Qt Test @@ -33,11 +27,17 @@ target_link_libraries(dfm-io-test ) # Add include directories -target_include_directories(dfm-io-test - PRIVATE - ${CMAKE_SOURCE_DIR}/src/dfm-io/dfm-io - ${CMAKE_SOURCE_DIR}/src/dfm-io/dfm-io/sort -) +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() # 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 deleted file mode 100644 index 1432aae5..00000000 --- a/autotests/dfm-io-tests/test_main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// 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 deleted file mode 100644 index 5308873a..00000000 --- a/autotests/dfm-io-tests/tst_dfilesorter.cpp +++ /dev/null @@ -1,640 +0,0 @@ -// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#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_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() -{ -} - -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_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; - 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")); -} - -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; - 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 17b33637..da783bca 100644 --- a/autotests/dfm-io-tests/tst_dfm_io.cpp +++ b/autotests/dfm-io-tests/tst_dfm_io.cpp @@ -30,10 +30,5 @@ void tst_DfmIO::initialization_test() QVERIFY(true); } -int run_tst_DfmIO(int argc, char *argv[]) -{ - tst_DfmIO tc; - return QTest::qExec(&tc, argc, argv); -} - +QTEST_MAIN(tst_DfmIO) #include "tst_dfm_io.moc" diff --git a/src/dfm-io/dfm-io/denumerator.cpp b/src/dfm-io/dfm-io/denumerator.cpp index bc14e046..6c129566 100644 --- a/src/dfm-io/dfm-io/denumerator.cpp +++ b/src/dfm-io/dfm-io/denumerator.cpp @@ -5,7 +5,6 @@ #include "private/denumerator_p.h" #include "utils/dlocalhelper.h" -#include "sort/dfilesorter.h" #include #include @@ -804,29 +803,14 @@ QList> DEnumerator::fileInfoList() QList> DEnumerator::sortFileInfoList() { - // 使用 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) + d->openDirByfts(); if (!d->fts) return {}; - // 收集所有文件信息 - QList> allFiles; + QList> listFile; + QList> listDir; QSet hideList; QUrl urlHidden = d->buildUrl(d->uri, ".hidden"); hideList = DLocalHelper::hideListFromUrl(urlHidden); @@ -835,8 +819,7 @@ QList> DEnumerator::sortFileInfoList() qWarning() << "Failed to get file path for uri:" << d->uri; return {}; } - - while (true) { + while (1) { FTSENT *ent = fts_read(d->fts); if (ent == nullptr) { @@ -851,30 +834,20 @@ QList> DEnumerator::sortFileInfoList() if (strcmp(ent->fts_path, dirPath) == 0 || flag == FTS_DP) continue; - auto sortInfo = DLocalHelper::createSortFileInfo(ent, hideList); - // 跳过子目录遍历 - if (sortInfo->isDir && !sortInfo->isSymLink) { - fts_set(d->fts, ent, FTS_SKIP); - } - allFiles.append(sortInfo); + d->insertSortFileInfoList(listFile, listDir, ent, d->fts, hideList); } fts_close(d->fts); d->fts = nullptr; + + // Clean up allocated memory free(dirPath); - // 使用 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)); + if (d->isMixDirAndFile) + return listFile; + + listDir.append(listFile); + return listDir; } DFMIOError DEnumerator::lastError() const diff --git a/src/dfm-io/dfm-io/sort/dfilesorter.cpp b/src/dfm-io/dfm-io/sort/dfilesorter.cpp deleted file mode 100644 index bb1adb17..00000000 --- a/src/dfm-io/dfm-io/sort/dfilesorter.cpp +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "dfilesorter.h" -#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) -{ - 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 -{ - // 获取有效大小:目录返回 -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 -{ - // 获取用于排序的修改时间:符号链接使用指向文件的时间 - 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 (timeA != timeB) { - return timeA < timeB; - } - if (nsA != nsB) { - return nsA < nsB; - } - // 时间相同,按名称排序 - return compareByName(a, b); -} - -bool DFileSorter::compareByLastRead( - 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 (timeA != timeB) { - return timeA < timeB; - } - if (nsA != nsB) { - return nsA < nsB; - } - // 时间相同,按名称排序 - 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) { - 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 deleted file mode 100644 index 09067af9..00000000 --- a/src/dfm-io/dfm-io/sort/dfilesorter.h +++ /dev/null @@ -1,139 +0,0 @@ -// 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 deleted file mode 100644 index 003ee411..00000000 --- a/src/dfm-io/dfm-io/sort/dsortkeycache.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// 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 deleted file mode 100644 index 48923278..00000000 --- a/src/dfm-io/dfm-io/sort/dsortkeycache.h +++ /dev/null @@ -1,75 +0,0 @@ -// 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