#include "PathRowProvider.h" #include #include #include "PathFuzzy.h" namespace { // How long the deeper walk stays valid. Long enough that typing a name pays for // it once, short enough that a file created elsewhere appears on its own. constexpr qint64 kDeepCacheMs = 5000; } // namespace PathRowProvider::PathRowProvider(QObject* parent) : FuzzyRowProvider(parent) {} void PathRowProvider::setIncludeFiles(bool include) { if (m_includeFiles == include) { return; } // The worker reads m_includeFiles under inputLock() while it ranks; the write // must take the same lock so it never sees a torn value. { QMutexLocker lock(&inputLock()); m_includeFiles = include; } emit includeFilesChanged(); invalidate(); } void PathRowProvider::setRoot(const QString& root) { if (m_root == root) { return; } // A new root makes the cached walk describe a different tree, so the cache // goes with it. { QMutexLocker lock(&inputLock()); m_root = root; m_deepRoot.clear(); m_deepStampMs = 0; } emit rootChanged(); invalidate(); } void PathRowProvider::setIncludeTerminalRow(bool include) { if (m_includeTerminalRow == include) { return; } { QMutexLocker lock(&inputLock()); m_includeTerminalRow = include; } emit includeTerminalRowChanged(); invalidate(); } const QStringList& PathRowProvider::deepPaths() { // Reached under inputLock(), so m_root is stable. const qint64 now = QDateTime::currentMSecsSinceEpoch(); if (m_deepRoot != m_root || now - m_deepStampMs > kDeepCacheMs) { m_deepWalk = walkPaths(m_root, PathScope::Files); m_deepRoot = m_root; m_deepStampMs = now; } return m_deepWalk.paths; } QVariantList PathRowProvider::rowsForQuery(const QString& query) { // Reached under inputLock(), so every input member is stable. const QString base = PathFuzzy::queryBase(query); const QString partial = PathFuzzy::queryPartial(query); const bool files = m_includeFiles; const PathScope scope = files ? PathScope::Entries : PathScope::Directories; // The primary listing: the directory the text names, one level deep. const WalkResult listing = walkPaths(base, scope); QVariantList rows = rankRows(listing.paths, base, partial, scope, files && m_includeTerminalRow); WalkLimit limit = listing.limit; if (files && !partial.isEmpty() && rows.isEmpty()) { // Nothing in the current directory matches, so look deeper from the root. // This is the only path that reads a whole tree, and deepPaths() caches it. // The partial is the search text, not the whole field: the field holds a // path, and a path never fuzzy-matches a file name. rows = rankRows(deepPaths(), m_root, partial, PathScope::Files, false); limit = m_deepWalk.limit; } // The note explains the set that was just built. An empty listing on a // non-empty fragment is the case a user meets when the name they typed is not // there, and silence there reads as a broken picker rather than an absent // file. if (rows.isEmpty() && !partial.isEmpty()) { setNote(QStringLiteral("nothing here matches \"%1\"").arg(partial)); return rows; } switch (limit) { case WalkLimit::None: setNote(QString()); break; case WalkLimit::Count: setNote( QStringLiteral("showing the first %1 entries").arg(kMaxWalkPaths)); break; case WalkLimit::Depth: setNote(QStringLiteral("searched %1 levels down").arg(kMaxWalkDepth)); break; } return rows; }