From efa0ab59bede4c2acdaff47df23410a09891804b Mon Sep 17 00:00:00 2001 From: Kirill Kostin <15322854+k-kostin@users.noreply.github.com> Date: Fri, 13 Nov 2026 14:50:08 +0300 Subject: [PATCH 1/2] Stop counting an overtaken folder inside its directory walk --- src/foldermodel.cpp | 16 +++++++++++----- src/foldermodel.h | 3 ++- tests/folio_tests.cpp | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/foldermodel.cpp b/src/foldermodel.cpp index 2cce5a3f..906d2707 100644 --- a/src/foldermodel.cpp +++ b/src/foldermodel.cpp @@ -431,11 +431,16 @@ void FolderModel::summarize(Entry &entry, bool showHidden) { } } -int FolderModel::itemCount(const QString &folder, bool showHidden) { +int FolderModel::itemCount(const QString &folder, bool showHidden, const std::function &stop) { + if (stop && stop()) + return -1; int count = 0; - for (QDirIterator it(folder, entryFilters(showHidden)); it.hasNext(); it.next()) + for (QDirIterator it(folder, entryFilters(showHidden)); it.hasNext(); it.next()) { + if (stop && stop()) + return -1; ++count; - return count; + } + return stop && stop() ? -1 : count; } void FolderModel::reload() { @@ -527,10 +532,11 @@ void FolderModel::countFolders(quint64 generation) { watcher->setFuture(QtConcurrent::run([folders, showHidden, latest, generation]() mutable { // Overtaken (another folder, or a re-read), the counts would be // thrown away, so it stops instead of listing every subfolder. + const auto overtaken = [&] { return *latest != generation; }; for (auto &folder : folders) { - if (*latest != generation) + if (overtaken()) break; - folder.second.itemCount = itemCount(folder.second.path, showHidden); + folder.second.itemCount = itemCount(folder.second.path, showHidden, overtaken); } return folders; })); diff --git a/src/foldermodel.h b/src/foldermodel.h index 681a2c7e..b8086947 100644 --- a/src/foldermodel.h +++ b/src/foldermodel.h @@ -120,7 +120,8 @@ class FolderModel : public QAbstractListModel { static void summarize(Entry &entry, bool showHidden); // Only how many items a folder holds, for the folder counts, which have // no use for its pictures or their order. - static int itemCount(const QString &folder, bool showHidden); + // Returns -1 (unknown) if stopped, never a partially counted folder. + static int itemCount(const QString &folder, bool showHidden, const std::function &stop = {}); static QStringList sortModes(); static void sortEntries(QVector &entries, const QString &sort, const QLocale &locale = QLocale()); // The Recents view: a path that isn't a folder, listing recently used files. diff --git a/tests/folio_tests.cpp b/tests/folio_tests.cpp index 21e81d07..9aa6b96e 100644 --- a/tests/folio_tests.cpp +++ b/tests/folio_tests.cpp @@ -561,6 +561,26 @@ private slots: QCOMPARE(FolderModel::itemCount(dir.path(), true), 5); } + void folderCountStopsInsideTheFolderAndCanBeRetried() + { + QTemporaryDir dir; + for (int i = 0; i < 64; ++i) + write(dir.filePath(QString::number(i) + ".txt"), "x"); + int checks = 0; + // Cancellation arrives during one folder, rather than between folders. + const int count = FolderModel::itemCount(dir.path(), false, [&] { return ++checks >= 4; }); + QCOMPARE(count, -1); // Unknown, never a partial count. + QVERIFY(checks >= 4); + QVERIFY(checks < 64); + QCOMPARE(FolderModel::itemCount(dir.path(), false), 64); + + // An empty or not-yet-started folder is unknown too once cancelled. + QTemporaryDir empty; + QCOMPARE(FolderModel::itemCount(empty.path(), false, [] { return true; }), -1); + QCOMPARE(FolderModel::itemCount(dir.path(), true, [] { return true; }), -1); + QCOMPARE(FolderModel::itemCount(empty.path(), false), 0); + } + void folderModelLoadsInTheBackground() { QTemporaryDir dir; From 1a7e72d98949c9a6d34cf5824363bd41d5939f96 Mon Sep 17 00:00:00 2001 From: Kirill Kostin <15322854+k-kostin@users.noreply.github.com> Date: Fri, 13 Nov 2026 14:56:13 +0300 Subject: [PATCH 2/2] Keep hidden directory entries cancellable while counting --- src/foldermodel.cpp | 10 ++++++++-- tests/folio_tests.cpp | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/foldermodel.cpp b/src/foldermodel.cpp index 906d2707..422de460 100644 --- a/src/foldermodel.cpp +++ b/src/foldermodel.cpp @@ -435,10 +435,16 @@ int FolderModel::itemCount(const QString &folder, bool showHidden, const std::fu if (stop && stop()) return -1; int count = 0; - for (QDirIterator it(folder, entryFilters(showHidden)); it.hasNext(); it.next()) { + // Let hidden entries through too: filtering a long run inside Qt would + // give this loop no chance to notice cancellation. On Linux a dot name + // is hidden, as in listRecent(). The iterator still excludes . and ... + QDirIterator it(folder, entryFilters(true)); + while (it.hasNext()) { if (stop && stop()) return -1; - ++count; + it.next(); + if (showHidden || !it.fileName().startsWith('.')) + ++count; } return stop && stop() ? -1 : count; } diff --git a/tests/folio_tests.cpp b/tests/folio_tests.cpp index 9aa6b96e..2cfbd839 100644 --- a/tests/folio_tests.cpp +++ b/tests/folio_tests.cpp @@ -579,6 +579,15 @@ private slots: QCOMPARE(FolderModel::itemCount(empty.path(), false, [] { return true; }), -1); QCOMPARE(FolderModel::itemCount(dir.path(), true, [] { return true; }), -1); QCOMPARE(FolderModel::itemCount(empty.path(), false), 0); + + // Hidden entries still take work to enumerate, even when not counted. + QTemporaryDir hidden; + for (int i = 0; i < 64; ++i) + write(hidden.filePath("." + QString::number(i) + ".txt"), "x"); + checks = 0; + QCOMPARE(FolderModel::itemCount(hidden.path(), false, [&] { return ++checks >= 4; }), -1); + QCOMPARE(FolderModel::itemCount(hidden.path(), false), 0); + QCOMPARE(FolderModel::itemCount(hidden.path(), true), 64); } void folderModelLoadsInTheBackground()