#include "WorkspaceScanner.h" #include #include #include #include namespace { struct PendingDirectory { QString path; int depth = 0; }; } WorkspaceScanner::WorkspaceScanner(int maxDepth, int maxEntries) : m_maxDepth(maxDepth) , m_maxEntries(maxEntries) { } QVector WorkspaceScanner::scan(const QString &workspaceRoot, QString *errorMessage) const { if (!WorkspaceAccessPolicy::validateWorkspaceRoot(workspaceRoot, errorMessage)) { return {}; } const WorkspaceAccessPolicy policy(workspaceRoot); QVector files; QVector pending; pending.append({policy.workspaceRoot(), 0}); while (!pending.isEmpty() && files.size() < m_maxEntries) { const PendingDirectory current = pending.takeLast(); QString directoryError; if (!policy.canReadDirectory(current.path, &directoryError)) { continue; } const QFileInfoList entries = QDir(current.path).entryInfoList( QDir::NoDotAndDotDot | QDir::AllEntries | QDir::Hidden, QDir::DirsFirst | QDir::Name); for (const QFileInfo &entry : entries) { if (files.size() >= m_maxEntries) { break; } if (entry.isHidden()) { continue; } const QString absolutePath = entry.absoluteFilePath(); if (entry.isDir()) { if (shouldSkipDirectory(policy, absolutePath)) { continue; } files.append(fileInfoForPath(policy, absolutePath)); if (current.depth + 1 <= m_maxDepth) { pending.append({absolutePath, current.depth + 1}); } continue; } if (entry.isFile()) { if (policy.hasSymlinkSegment(absolutePath)) { continue; } files.append(fileInfoForPath(policy, absolutePath)); } } } return files; } bool WorkspaceScanner::shouldSkipDirectory(const WorkspaceAccessPolicy &policy, const QString &path) const { const QFileInfo info(path); const QString name = info.fileName().toLower(); static const QStringList skipped = { QStringLiteral(".git"), QStringLiteral(".vs"), QStringLiteral(".idea"), QStringLiteral("build"), QStringLiteral("cmake-build-debug"), QStringLiteral("cmake-build-release"), QStringLiteral("node_modules"), QStringLiteral("dist"), QStringLiteral("release"), QStringLiteral("release_packages"), QStringLiteral("logs"), }; return skipped.contains(name) || policy.isSystemPath(path) || policy.hasSymlinkSegment(path); } WorkspaceFileInfo WorkspaceScanner::fileInfoForPath(const WorkspaceAccessPolicy &policy, const QString &path) const { const QFileInfo info(path); WorkspaceFileInfo file; file.absolutePath = policy.normalizePath(path); file.relativePath = policy.toRelativePath(file.absolutePath); file.fileName = info.fileName(); file.suffix = info.suffix(); file.sizeBytes = info.size(); file.lastModified = info.lastModified(); file.isDirectory = info.isDir(); file.isTextFile = !file.isDirectory && policy.isSupportedTextFile(file.absolutePath) && !policy.isSensitiveFile(file.absolutePath); file.isHidden = info.isHidden(); return file; }