diff --git a/src/foldermodel.cpp b/src/foldermodel.cpp index 2cce5a3..422de46 100644 --- a/src/foldermodel.cpp +++ b/src/foldermodel.cpp @@ -431,11 +431,22 @@ 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()) - ++count; - return count; + // 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; + it.next(); + if (showHidden || !it.fileName().startsWith('.')) + ++count; + } + return stop && stop() ? -1 : count; } void FolderModel::reload() { @@ -527,10 +538,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 681a2c7..b808694 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 21e81d0..2cfbd83 100644 --- a/tests/folio_tests.cpp +++ b/tests/folio_tests.cpp @@ -561,6 +561,35 @@ 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); + + // 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() { QTemporaryDir dir;